Fullstack React Native
The Complete Guide to React Native
Wri!en by Devin Abbo!, Houssein Djirdeh, Anthony Accomazzo, and Sophia
Shoemaker
© 2017 Fullstack.io
All rights reserved. No portion of the book manuscript may be reproduced, stored in a retrieval
system, or transmi!ed in any form or by any means beyond the number of purchased copies,
except for a single backup or archival copy. "e code may be used freely in your projects,
commercial or otherwise.
"e authors and publisher have taken care in preparation of this book, but make no expressed
or implied warranty of any kind and assume no responsibility for errors or omissions. No
liability is assumed for incidental or consequential damagers in connection with or arising out
of the use of the information or programs container herein.
Published in San Francisco, California by Fullstack.io.
FULLSTACK
.io
Contents
Book Revision . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Bug Reports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Be notified of updates via Twitter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
We’d love to hear from you! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
About This Book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Running Code Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Code Blocks and Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Getting Help . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Emailing Us . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Getting Started with React Native . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Weather App . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Starting the project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Expo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Custom components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
React Fundamentals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Breaking the app into components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
7 step process . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Step 2: Build a static version of the app . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Step 3: Determine what should be stateful . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Step 4: Determine in which component each piece of state should live . . . . . . . . . . . . 93
Step 5: Hardcode initial states . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
Step 6: Add inverse data flow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
Updating timers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
Deleting timers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
Adding timing functionality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
Add start and stop functionality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
Methodology review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
Core Components, Part 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
CONTENTS
What are components? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
Building an Instagram clone . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
StyleSheet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
TouchableOpacity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
Image . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
ActivityIndicator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
FlatList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
Core Components, Part 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
TextInput . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
ScrollView . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
Modal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
Core APIs, Part 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
Building a messaging app . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
Initializing the project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
The app . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
Network connectivity indicator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
The message list . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
Toolbar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
Geolocation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
Input Method Editor (IME) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
Core APIs, Part 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
The keyboard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
We’re Done! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310
Navigation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
Navigation in React Native . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
Contact List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
Starting the project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322
Container and Presentational components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324
Contacts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324
Profile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328
React Navigation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332
Stack navigation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332
Tab navigation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344
Drawer navigation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 364
Sharing state between screens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
Deep Linking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
CONTENTS
Animation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384
Animation challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384
Building a puzzle game . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386
App . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390
Building the Start screen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392
Building the Game screen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 429
Gestures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 430
Building the board . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 430
Gesture Responder System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 442
PanResponder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446
Draggable component . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447
Finishing the game . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 461
We’re Done! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466
Native Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467
What are native modules? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467
Building a native module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 469
Development environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 471
Initializing the project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 472
iOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 477
Android . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 487
JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 496
Building and publishing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 504
How to read this chapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 504
Building . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 504
Building with Expo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505
iOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 508
Android . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 525
Handling Updates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 532
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 533
Appendix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 534
JavaScript Versions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 534
ES2015 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 534
ReactElement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 540
Handling Events in React Native . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 541
Publishing with Expo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 545
Changelog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 546
CONTENTS 1
Book Revision
Revision 5 - Native modules chapter added to book
Bug Reports
If you’d like to report any bugs, typos, or suggestions just email us at: rn@fullstack.io
1
.
Be notified of updates via Twitter
If you’d like to be notified of updates to the book on Twitter, follow @fullstackreact
2
We’d love to hear from you!
Did you like the book? Did you find it helpful? We’d love to add your face to our list of testimonials
on the website! Email us at: rn@fullstack.io
3
.
1
mailto:rn@fullstack.io?Subject=Fullstack%20React%20Native%20book%20feedback
2
https://twitter.com/fullstackreact
3
mailto:rn@fullstack.io?Subject=React%20Native%20testimonial
Introduction
One of the major problems that teams face when writing native mobile applications is becoming
familiar with all the different technologies. iOS and Android - the two dominant mobile platforms
- support different languages. For iOS, Apple supports the languages Swift
4
and Objective-C
5
. For
Android, Google supports the languages Java
6
and Kotlin
7
.
And the differences don’t end there. These platforms have different toolchains. And they have
different interfaces for the device’s core functionality. Developers have to learn each platform’s
procedure for things like accessing the camera or checking network connectivity.
One trend is to write mobile apps that are powered by WebViews. These types of apps have minimal
native code. Instead, the interface is a web browser running an app written in HTML, CSS, and JS.
This web app can use the native wrapper to access features on the device, like the camera roll.
Tools like Cordova
8
enable developers to write these hybrid apps. The advantage is that developers
can write apps that run on multiple platforms. Instead of learning iOS and Android specifics, they
can use HTML, CSS, and JS to write a “universal” app.
The disadvantage, though, is that it’s hard to make these apps look and feel like real native
applications. And users can tell.
While universal WebView-powered apps were built with the idea of build once, run anywhere, React
Native was built with the goal of learn once, write anywhere.
React is a JavaScript framework for building rich, interactive web applications. With React
Native, we can build native mobile applications for multiple platforms using JavaScript and React.
Importantly, the interfaces we build are translated into native views. React Native apps are not
composed of WebViews.
We’ll be able to share a lot of the code we write between iOS and Android. And React Native makes
it easy to write code specific to each platform when the need arises. We get to use one language
(JavaScript), one framework (React), one styling engine, and one toolchain to write apps for both
platforms. Learn once, write anywhere.
At its core, React Native is composed of React components. We’ll dig deep into components
throughout this book, but here’s an example of what a React component looks like:
4
https://developer.apple.com/swift/
5
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/Introduction/
Introduction.html
6
https://docs.oracle.com/javase/8/docs/technotes/guides/language/index.html
7
https://developer.android.com/kotlin/index.html
8
https://cordova.apache.org/
Introduction 2
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default class StyledText extends React.Component {
render() {
return (
<Text style={styles.text}>{content}</Text>
);
}
}
const styles = StyleSheet.create({
text: {
color: 'red',
fontWeight: 'bold',
},
});
React Native works. It is currently being used in production at Facebook, Instagram, Airbnb, and
thousands of other companies.
About This Book
This book aims to be an extensive React Native resource. By the time you’re done reading this book,
you (and your team) will have everything you need to build reliable React Native applications.
React Native is rich and feature-filled, but that also means it can be tricky to understand all of its
parts. In this book, we’ll walk through everything, such as installing its tools, writing components,
navigating between screens, and integrating native modules.
But before we dig in, there are a few guidelines we want to give you in order to get the most out of
this book. Specifically:
how to approach the code examples and
how to get help if something goes wrong
Running Code Examples
This book comes with a library of runnable code examples. The code is available to download from
the same place where you downloaded this book.
We use yarn
9
to run every example in this book. This means you can type the following commands
to run any example:
9
https://yarnpkg.com/en/
Introduction 3
yarn start will start the React Native packager and print a QR code. If you’re on an Android
mobile device, scanning this code with the Expo
10
app will load the application. For iOS devices,
see the instructions for loading apps onto your phone at the beginning of the first chapter.
yarn run ios will start the React Native packager and open your app in the iOS Simulator if
you are using a Mac.
yarn run android will start the React Native packager and open your app on a connected
Android device or emulator.
In the next chapter we’ll explain each of these commands in detail.
Code Blocks and Context
Nearly every code block in this book is pulled from a runnable code example, which you can find
in the sample code. For example, here is a code block pulled from the first chapter:
weather/1/App.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
<Text>Changes you make will automatically reload.</Text>
<Text>Shake your phone to open the developer menu.</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
10
https://expo.io/
Introduction 4
Notice that the header of this code block states the path to the file which contains this code:
code/weather/1/App.js.
This book is written with the expectation that you’ll also be looking at the example code
alongside the chapter. If you ever feel like you’re missing the context for a code example, open up
the full code file using your favorite text editor.
For example, we often need to import libraries to get our code to run. In the early chapters of the
book we show these import statements, because it’s not clear where the libraries are coming from
otherwise. However, the later chapters of the book are more advanced and they focus on key concepts
instead of repeating boilerplate code that was covered earlier in the book. If at any point you’re not
clear on the context, open up the code example on disk.
Getting Help
While we’ve made every effort to be clear, precise, and accurate you may find that when you’re
writing your code you run into a problem.
Generally, there are three types of problems:
A “bug” in the book (e.g. something is explained incorrectly)
A “bug” in our code
A “bug” in your code
If you find an inaccuracy in our description of something, or you feel a concept isn’t clear, email us!
We want to make sure that the book is both accurate and clear.
Similarly, if you’ve found a bug in our code we definitely want to hear about it.
If you’re having trouble getting your own app working (and it isn’t our example code), this case is a
bit harder for us to handle. If you’re still stuck, we’d still love to hear from you, and here some tips
for getting a clear, timely response.
Emailing Us
If you’re emailing us asking for technical help, here’s what we’d like to know:
What revision of the book are you referring to?
What operating system are you on? (e.g. Mac OS X 10.8, Windows 95)
Which chapter and which example project are you on?
What were you trying to accomplish?
What have you tried already?
What output did you expect?
Introduction 5
What actually happened? (Including relevant log output.)
The absolute best way to get technical support is to send us a short, self-contained example of the
problem. Our preferred way to receive this would be for you to send us an Expo Snack link
11
. Snack
is an online code editor that let’s one quickly develop and demo React Native components on the
browser or an actual device without having to set up a brand new project. We’ll explain Expo in
more detail in the next chapter.
When you’ve written down these things, email us at rn@fullstack.io. We look forward to hearing
from you.
11
https://snack.expo.io/
Getting Started with React Native
Weather App
In this chapter we’re going to build a weather application that allows the user to search for any city
and view its current forecast.
With this simple app we’ll cover some essentials of React Native including:
Using core and custom components
Passing data between components
Handling component state
Handling user input
Applying styles to components
Fetching data from a remote API
By the time we’re finished with this chapter, you’ll know how to get started with Create React Native
App and build a basic application with local state management. You’ll have the foundation you need
to build a wide variety of your own React Native apps.
Here’s a screenshot of what our app will look like when it’s done:
Getting Started with React Native 7
The completed app
In this chapter, we’ll build an entire React Native application from scratch. We’ll talk about how to
set up our development environment and how to initialize a new React Native application. We’ll
also learn how Expo allows us to rapidly prototype and preview our application on our mobile
device. After covering some of the basics of React Native, we’ll explore how we compose apps using
components. Components are a powerful paradigm for organizing views and managing dynamic
data.
We’re about to touch on a wide variety of topics, like styling and data management. This chapter
will exhibit how all these topics fit together at a high-level. In subsequent chapters, we’ll dive deep
into the concepts that we touch on here.
Code examples
This book is example-driven. Each chapter is setup as a hands-on tutorial.
We’ll be building apps from the ground up. Included with this book is a download that contains
completed versions of each app as well as each of the versions we develop along the way (the “sample
code.”) If you’re following along, we recommend you use the sample code for copying and pasting
longer examples or debugging unexpected errors. If you’re not following along, you can refer to the
sample code for more context around a given code example.
The structure of the sample code for all the chapters in this book follows this pattern:
Getting Started with React Native 8
├── components/
├── App.js
├── 1/
├── components/
└── App.js
├── 2/
├── components/
└── App.js
├── 3/
├── components/
└── App.js
// ...
At the top-level of the directory is App.js and components/. This is the code for the completed
version of the application. Inside the numbered folders (1/, 2/, 3/) are the different versions of the
app as we build it up throughout the chapter.
Here’s what a code example in this book looks like:
weather/1/App.js
render() {
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
<Text>Changes you make will automatically reload.</Text>
<Text>Shake your phone to open the developer menu.</Text>
</View>
);
}
Note that the title of the code block contains the path within the sample code where you can find
this example (weather/1/App.js).
JavaScript
This book assumes some JavaScript knowledge.
React Native uses Babel
12
as a JavaScript compiler to allow us to develop in the latest version
of JavaScript, ES2016. To understand what we mean by JavaScript versions, you can refer to the
Appendix.
We highlight some of JavaScript’s newer features in the Appendix. We reference the appendix when
relevant.
12
https://babeljs.io/
Getting Started with React Native 9
Starting the project
Create React Native App
To begin, we’re going to use Create React Native App
13
(CRNA), a tool that makes it extremely easy
to get started with React Native. If you’ve used Create React App
14
before, you’ll notice similarities
here in that no build configuration is required to get up and running. We can install it globally using
yarn
15
.
yarn
yarn
16
is a node package manager that automates the process of managing all the required
dependencies and packages from npm, an online repository of published JavaScript libraries and
projects, in an application. This is done by defining all our dependencies in a single package.json
file.
npm also has a command line tool, npm, that allows us to maintain and control dependencies.
The tool that we use to build our application, CRNA, does not currently work with the latest
version of npm, npm v5. For this reason, we’ll use yarn throughout the book.
You can refer to the documentation
17
for instructions to install yarn for your operating system. The
documentation also explains how to install node
18
as well. In order to use CRNA however, Node.js
v6 or later is required.
Here’s a list of some commonly used yarn commands:
yarn init creates a package.json file and adds it directly to our project.
yarn installs all the dependencies listed in package.json into a local node_modules folder.
yarn add new-package will install a specific package to our project as well as include it as a
dependency in package.json. Dependencies are packages needed when we run our code.
yarn add new-package --dev will install a specific package to our project as well as include it as
a development dependency in package.json. Development dependencies are packages needed
only during the development workflow. They are not needed for running our application in
production.
yarn global add new-package will install the package globally, rather than locally to a specific
project. This is useful when we need to use a command line tool anywhere on our machine.
13
https://github.com/react-community/create-react-native-app
14
https://github.com/facebookincubator/create-react-app
15
https://yarnpkg.com
16
https://yarnpkg.com
17
https://yarnpkg.com/lang/en/docs/install
18
https://nodejs.org/en/
Getting Started with React Native 10
If we already have an older version of npm than v5 installed, we can use it instead of yarn
and run its equivalent commands
19
.
Watchman
Watchman
20
is a file watching service that watches files and triggers actions when they are modified.
If you use macOS as your operating system, the Expo and React Native documentation recommend
installing Watchman for better performance. The instructions to install the service can be found
here
21
.
Expo
Expo
22
is a platform that provides a number of different tools to build fully functional React Native
applications without having to write native code. Beginning a project with CRNA automatically
creates an application that leverages Expo’s development environment.
A benefit of leveraging Expo is that building an application does not require using Xcode for iOS, or
Android Studio for Android. This means that developers can build native iOS applications without
even owning a Mac computer. Using CRNA and Expo is the easiest way to get started with React
Native and is recommended in the React Native documentation
23
.
Including Native Code
Using Expo and CRNA isn’t the only way to start a React Native application. If we need to start
a project with the ability to include native code, we’ll need to use the React Native CLI
a
instead.
With this however, our application will require Xcode and Android Studio for iOS and Android
respectively.
Expo also provides a number of different APIs for device specific properties such as contacts, camera
and video. However, if we need to include a native iOS or Android dependency that is not provided
by Expo, we’ll need to eject from the platform entirely. Ejecting an Expo application means we have
full control of managing our native dependencies, but we would need to use the React Native CLI
from that point on.
We’ll explore how to add native modules onto a React Native project later on this book.
a
https://facebook.github.io/react-native/docs/getting-started.html#installing-dependencies
19
https://yarnpkg.com/lang/en/docs/migrating-from-npm/#toc-cli-commands-comparison
20
https://facebook.github.io/watchman/
21
https://facebook.github.io/watchman/docs/install.html#installing-on-os-x-via-homebrew
22
https://expo.io/
23
https://facebook.github.io/react-native/docs/getting-started.html
Getting Started with React Native 11
Previewing the app
To develop and preview apps with Expo, we need to install its client iOS or Android app
24
to develop
and run React Native apps on our device.
Android
On your Android mobile device, install the Expo Client on Google Play
25
. You can then select Scan
QR code and scan this QR code once you’ve installed the app:
QR Code
If this QR code doesn’t work, we recommend making sure you have the latest version of that Expo
app installed, and that you’re reading the latest edition of this book.
Instead of scanning the QR code, you can also type the project URL,
exp://exp.host/@fullstackio/weather
, inside of Expo to load the application.
iOS
You can install the Expo Client via the App Store
26
. With an iOS device however, there is no capability
to scan a QR code. This means we’ll first need to build the final app in order to preview it. We can
do this by navigating to the weather/ directory in the sample code folder and running the following
commands:
cd weather
yarn
yarn start
This will start the React Native packager. Pressing s will allow you to send a link to your device
by SMS or e-mail (you’ll need to provide your mobile phone number of email address). Once done,
clicking the link will open the application in the Expo Client.
24
https://github.com/expo/expo
25
https://play.google.com/store/apps/details?id=host.exp.exponent
26
https://itunes.apple.com/us/app/expo-client/id982107779?mt=8
Getting Started with React Native 12
For the app to load on your physical device, you’ll need to make sure that your phone is connected
to the same local network as your computer.
Local Development Tool
In addition to a client app, Expo also provides two local development tools that allow us to
preview, share and publish our projects:
XDE
27
, or the Expo Development Environment, is a desktop app that we can use for
macOS, Windows, or Linux.
exp
28
is a command line interface.
Both options provide a number of different commands and services that we can use to
manage our applications. Instead of using the s hotkey provided by CRNA when we start
the packager to send the link to an iOS device, we can also use exp or XDE instead.
We’ll explore using these local development tools in more detail when we cover deploying
and publishing apps later in this book.
Preparing the app
At this point, you should see the final application load successfully on your device. Play around with
the app for a few minutes to get a feel for it. Try searching for different cities as well a location that
doesn’t even exist.
If you plan on building the application as you read through the chapter, you’ll need to create a brand
new project. Once yarn is installed, let’s run the following command to install Create React Native
App (CRNA) globally:
yarn global add create-react-native-app@1.0.0
The @1.0.0 specifies the version of create-react-native-app to install. It’s important to
lock in version 1.0.0 so that the version on your machine matches that here in the book.
We’ll call our application weather and can use the following command to get started (this command
may take a little while):
create-react-native-app weather --scripts-version 1.14.0
27
https://github.com/expo/xde
28
https://docs.expo.io/versions/latest/workflow/exp-cli
Getting Started with React Native 13
Importantly, we specify the --scripts-version as 1.14.0. We’ll talk about this in a moment.
We’ll then navigate to that directory and boot the app:
cd weather
yarn start
With the pacakger running, we can continue to scan the QR code with an Android device or send a
link directly to our iOS device using the s hotkey. It is important to remember that our device needs
to be connected to the same local network as our computer in order for this to work.
Right now, viewing the app shows our starting point:
Application
Getting Started with React Native 14
Running on a simulator
As we mentioned, using the Expo client app allows us to run our application without using
native tooling (Xcode for iOS, or Android Studio for Android).
However if we happen to have the required build tools we can still run our application in a
virtual device or simulator:
With a Mac, yarn run ios will start the development server and run the application
in an iOS simulator. We can also start the packager separately with yarn start and
press i to open the simulator.
With the required Android tools
29
, yarn run android will start the application in an
Android emulator. Similarly, pressing a when the React Native packager is running
will also boot up the emulator.
Running an application using an emulator/simulator can be useful to test on different devices
and screen sizes. It can also be quicker to update and test code changes on a virtual device.
However, it’s important to run your application on an actual device at some point in order
to get a better idea of how exactly it looks and feels.
By default, CRNA comes with live reload enabled. This means if you edit and save any file, the
application on your mobile device will automatically reload. Moreover, any build errors and logs
will be displayed directly in the terminal.
Let’s see what the directory structure of our app looks like. Open up a new terminal window.
Navigate to this app:
cd weather
And then run ls -a to see all the contents of the directory:
ls -a
If you’re using PowerShell or another non-Unix shell, you can just run ls.
Although your output will look slightly different based on your operating system, you should see
all the files in your directory listed:
29
https://facebook.github.io/react-native/docs/getting-started.html
Getting Started with React Native 15
├── node_modules/
├── .babelrc
├── .flowconfig
├── .gitignore
├── .watchmanconfig
├── App.js
├── app.json
├── App.test.js
├── package.json
├── README.md
└── yarn.lock
Let’s go through each of these files:
node_modules/ contains all third party packages in our application. Any new dependencies and
development dependencies go here.
.babelrc allows us to define presets and plugins for configuring Babel
30
. As we mentioned
previously, Babel is a transpiler that compiles newer experimental JavaScript into older versions
so that it stays compatible with different platforms.
.flowconfig allows us to configure Flow
31
, a static type checker for JavaScript. Flow is part
of the React Native toolchain and this file is included automatically in any React Native
application. We won’t be using Flow in this chapter but we will explore prop validations briefly
using prop-types.
.gitignore is where we specify which files should be ignored by Git. We can see that both the
node_modules/ and .expo/ directories are already included.
.watchmanconfig defines configurations for Watchman.
App.js is where our application code lives.
app.json is a configuration file that allows us to add information about our Expo app. The list
of properties that can be included in this file is listed in the documentation
32
.
App.test.js is included as a sample test file and contains a single test. CRNA is packaged with
Jest
33
as its testing platform. We’ll go into detail about unit testing React Native applications
in the “Testing” chapter.
package.json is where we provide information of the application to our package manager as
well as specify all our project dependencies.
README.md is a markdown file commonly used to provide a description of a project.
yarn.lock is where yarn keeps a record of the versions of each dependency installed.
package.json
Let’s take a closer look at the generated package.json file:
30
https://babeljs.io/
31
https://flow.org/
32
https://docs.expo.io/versions/v18.0.0/guides/configuration.html
33
https://facebook.github.io/jest/
Getting Started with React Native 16
1 {
2 "name": "weather",
3 "version": "0.1.0",
4 "private": true,
5 "devDependencies": {
6 "react-native-scripts": "1.14.0",
7 "jest-expo": "~27.0.0",
8 "react-test-renderer": "16.3.1"
9 },
10 "main": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
11 "scripts": {
12 "start": "react-native-scripts start",
13 "eject": "react-native-scripts eject",
14 "android": "react-native-scripts android",
15 "ios": "react-native-scripts ios",
16 "test": "jest"
17 },
18 "jest": {
19 "preset": "jest-expo"
20 },
21 "dependencies": {
22 "expo": "^27.0.1",
23 "react": "16.3.1",
24 "react-native": "~0.55.2"
25 }
26 }
The name and version properties are always required. The dependencies and devDependencies
define our application and development dependencies respectively.
Notice there are three devDependencies:
react-native-scripts
jest-expo
react-test-renderer
The last two packages are related to testing. While CRNA is the tool that initializes our project,
the package react-native-scripts is the engine that runs our React Native app while in
development. When we specified the --scripts-version as 1.14.0 above, we were referring to
this package.
In our package.json, the scripts object defines all our script commands. These commands are all
handled by the react-native-scripts package. The commands yarn run start, yarn run android,
and yarn run ios allow us to start our application development server and/or run on a virtual device
or simulator. The scripts object also contains two other commands:
Getting Started with React Native 17
yarn test runs all the Jest tests in our application
yarn run eject starts the process of ejecting our application from the CRNA toolchain. As
we mentioned earlier, this can be necessary if we need to include a React Native library that
contains native code or if we need to write native code ourselves.
The utils/ directory
If you look at the book’s sample code, you’ll note that every application has a utils/ directory. This
directory contains helper functions that the application will use. You don’t need to concern yourself
with the details of these functions as they’re not relevant to the chapter’s core concepts.
When we reach the point in the application’s development where we need to use a utility provided
by utils/, we’ll remind you to copy over that folder from the sample code. You can also do this
immediately after initializing each project.
Components
With newer versions of JavaScript, we can define objects with properties using classes. React
Native lets us use this syntax to create components. Let’s take a look at a visual breakdown of the
components in our application:
Getting Started with React Native 18
Component Structure
We have an App component that represents the entire screen and contains the weather information
displayed to the use. Inside of this component, we have a SearchInput component that allows us to
search for different cities.
App
App is the first component created with a default CRNA application. Let’s take a look at its file:
Getting Started with React Native 19
weather/1/App.js
1 import React from 'react';
2 import { StyleSheet, Text, View } from 'react-native';
3
4 export default class App extends React.Component {
5 render() {
6 return (
7 <View style={styles.container}>
8 <Text>Open up App.js to start working on your app!</Text>
9 <Text>Changes you make will automatically reload.</Text>
10 <Text>Shake your phone to open the developer menu.</Text>
11 </View>
12 );
13 }
14 }
15
16 const styles = StyleSheet.create({
17 container: {
18 flex: 1,
19 backgroundColor: '#fff',
20 alignItems: 'center',
21 justifyContent: 'center',
22 },
23 });
Notice how we have a class defined in our file named App that extends React.Component. Using
extends allows us to declare a class as a subclass of another class. In here, we’ve defined App as
a subclass of React.Component. This is how we specify a specific class to be a component in our
application.
If you’d like to learn more about how classes work in JavaScript, refer to our Appendix.
We can also attach methods as properties to classes, and the same applies to component classes in
React Native. We can see we already have one for this component, the render method:
Getting Started with React Native 20
weather/1/App.js
5 render() {
6 return (
7 <View style={styles.container}>
8 <Text>Open up App.js to start working on your app!</Text>
9 <Text>Changes you make will automatically reload.</Text>
10 <Text>Shake your phone to open the developer menu.</Text>
11 </View>
12 );
13 }
What we see on our device when launching our device matches what we see described in this
method. The render() method is the only required method for a React Native component. React
Native uses the return value from this method to determine what to render for the component.
When we use React Native, we represent different parts of our application as components. This
means we can build our app using different reusable pieces of logic with each piece displaying a
specific part of our UI. Let’s break down what we already have in terms of components:
Our entire application is rendered with App as our top-level component. Although created
automatically as part of setting up a new CRNA project, this component is a custom component
responsible for rendering what we need in our application.
The View component is used as a layout container.
Within View, we use the Text component to display lines of text in our application. Unlike App,
both View and Text are built-in React Native components that are imported and used in our
custom component.
We can see that our App component uses and returns an HTML-like structure. This is JSX, which is
an extension of JavaScript that allows us to use an XML-like syntax to define our UI structure.
JSX
When we build an application with React Native, components ultimately render native views which
are displayed on our device. As such, the render() method of a component needs to describe how
the view should be represented. In other words, React Native allows us to describe a component’s
iOS and Android representation in JavaScript.
JSX was created to make the JavaScript representation of components easier to understand. It allows
us to structure components and show their hierarchy visually in markup. Consider this JSX snippet:
Getting Started with React Native 21
<View>
<Text style={{ color: 'red' }}>
Hello, friend! I am a basic React Native component.
</Text>
</View>
In here, we’ve nested a Text component within a View component. Notice how we use braces ({})
around an object ({ color: 'red' }) to set the style property value for Text. In JSX, braces are a
delimiter, signaling to JSX that what resides in-between the braces is a JavaScript expression. The
other delimiter is using quotes for strings, like this:
<TextInput placeholder="This is a string" />
Even though the JSX above might look similar to HTML, it is actually just compiled into
JavaScript function calls (ex: React.createElement(View)). For this reason, we need to
import React at the top of any file that contains JSX. You can refer to the Appendix for
more detail.
During runtime React Native takes care of rendering the actual native UI for each compo-
nent.
Props
We use the imported Text component to wrap each line of text output for our App component:
<Text>Open up App.js to start working on your app!</Text>
And we use the imported View component to wrap all the Text components:
<View style={styles.container}>
...
</View>
Props allow us to pass parameters to components to customize their features. Here, View is used
to layout the entire content of the screen. We only have a single prop attached, style, that allows
us to pass in style parameters to adjust how our View component is rendered on our devices. Each
built-in component provided by React Native has its own set of valid props that we can use for
customization.
Getting Started with React Native 22
If you’re familiar with HTML, it’s very similar. For example, in HTML, say you wanted to insert an
image named image.png. You’d specify an img tag with a src attribute like this:
<img src="path/to/image.png">
To give you an idea of the similarity, in React Native we can include images using the Image
component. We specify the location using the source prop:
<Image source={require('./image.png')}>
We’ll cover images in greater detail later.
Like our View component, many components in React Native accept a style prop. Styling is a large
topic that we explore throughout this book. However, we can take a look at our styles object at the
bottom of App.js and get an idea of how it works:
weather/1/App.js
16 const styles = StyleSheet.create({
17 container: {
18 flex: 1,
19 backgroundColor: '#fff',
20 alignItems: 'center',
21 justifyContent: 'center',
22 },
23 });
Web developers may recognize that this looks like CSS (Cascading Style Sheets) which is used to
style web pages. It’s important to note that styling in React Native does not use CSS. However, React
Native borrows a lot of styling nomenclature from web development. Here, we specify that the text
should be centered and that the background color should be white (#fff).
If you’ve used CSS before, you’ll find styling in React Native very familiar. If not, don’t
worry! It’s easy to get the hang of it.
Specifically, styles.container has the attributes flex, alignItems and justifyContent.
These are used to position the
View
in the center of the screen. React Native uses
flexbox
to
layout and align items consistently on different device sizes. We’ll go into more detail about
how exactly flexbox works in later chapters.
Getting Started with React Native 23
To build our weather app, we’ll start with layout and styling. Once we have some of the essence of
our weather app in place we can begin to explore strategies for managing data.
As we saw in the completed version of the app, we want our app to display the city, temperature,
and weather conditions as separate text fields. Although we’ll eventually interface with a weather
API in order to retrieve actual data, we’ll begin with hard-coding these values.
The completed app
Adding styles
To get a better handle on styling, let’s try adding an object with a color attribute to one of the text
fields:
<View style={styles.container}>
<Text style={{ color: 'red' }}>
Open up App.js to start working on your app!
</Text>
<Text>Changes you make will automatically reload.</Text>
<Text>Shake your phone to open the developer menu.</Text>
</View>
Getting Started with React Native 24
Note that the outer-most set of brackets above are delimiters enclosing our JavaScript
statement. Inside of the delimiters is a JavaScript object. In React Native, if the object is
small enough it’s common to just write it all on one line.
However, the double brackets ({{}}) might be confusing. Here’s another way of writing the
same component:
const style = { color: 'red' };
return (
<View style={styles.container}>
<Text style={style}>
Open up App.js to start working on your app!
</Text>
Save App.js. We can see our style applied once the application reloads:
Getting Started with React Native 25
As we mentioned previously, live reload is enabled by default in Expo. This means that with
any change to the code, the application will reload immediately. If you happen to not see any
changes reflected as soon as you save the file, you may have to check to see if this is enabled.
The documentation
34
explains how to open up the developer menu and enable/disable the
feature.
Although we can style our entire component this way, a lot of inline styles (or style attributes defined
directly within the delimeter of the style prop) used in a component can make things harder to read
and digest.
We can solve this by leveraging React Native’s Stylesheet API to separate our styles from our
component. With Stylesheet, we can create styles with attributes similar to CSS stylesheets. We
can see that Stylesheet is already imported at the top of the file. It’s used to declare our first style,
styles.container, which we use for View. We can add a new style called red to our styles:
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
red: {
color: 'red',
},
});
We’ll then have Text use this style:
<View style={styles.container}>
<Text style={styles.red}>
Open up App.js to start working on your app!
</Text>
<Text>Changes you make will automatically reload.</Text>
<Text>Shake your phone to open the developer menu.</Text>
</View>
If we save our file and take a look at our app, we can see that the end result is the same.
Now let’s add some appropriate styles and text fields in order to display some weather data for a
location. To add multiple styles to a single component, we can pass in an array of styles:
34
https://docs.expo.io/versions/latest/guides/up-and-running.html#cant-see-your-changes
Getting Started with React Native 26
weather/2/App.js
14 <Text style={[styles.largeText, styles.textStyle]}>San Francisco</Text>
15 <Text style={[styles.smallText, styles.textStyle]}>Light Cloud</Text>
16 <Text style={[styles.largeText, styles.textStyle]}>24°</Text>
It is important to mention that when passing an array, the styles at the end of the array take
precedence over earlier styles, in case of any repeated attributes. We can see that we’re referencing
three new styles; textStyle, smallText, and largeText. Let’s define these within our styles object:
weather/2/App.js
29 const styles = StyleSheet.create({
30 container: {
31 flex: 1,
32 backgroundColor: '#fff',
33 alignItems: 'center',
34 justifyContent: 'center',
35 },
36 textStyle: {
37 textAlign: 'center',
38 fontFamily: Platform.OS === 'ios' ? 'AvenirNext-Regular' : 'Roboto',
39 },
40 largeText: {
41 fontSize: 44,
42 },
43 smallText: {
44 fontSize: 18,
45 },
textStyle specifies an alignment (center) as well as the fontFamily. Notice how we use
Platform to define platform specific fonts for both iOS and Android. We do this because both
operating systems provide a different set of native fonts.
smallText and largeText both specify different font sizes.
Platform is a built-in React Native API. We’ll need to make sure to import it:
Getting Started with React Native 27
weather/2/App.js
1 import React from 'react';
2 import {
3 StyleSheet,
4 Text,
5 KeyboardAvoidingView,
6 Platform,
7 TextInput,
8 } from 'react-native';
Let’s take a look at our application now:
Styled Text
Platform specific properties
The Platform API allows us to conditionally apply different styles or properties in our component
based on the device’s operating system. The OS attribute of the object returns either iOS or android
depending on the user’s device.
Getting Started with React Native 28
Although this is a relatively simple way to apply different properties in our application based on
the user’s device, there may be scenarios where we may want our component to be substantially
different between operating systems.
We can also use the Platform.select method that takes the operating system as keys within an
object and returns the correct result based on the device:
1 textStyle: {
2 textAlign: 'center',
3 ...Platform.select({
4 ios: {
5 fontFamily: 'AvenirNext-Regular',
6 },
7 android: {
8 fontFamily: 'Roboto',
9 },
10 }),
11 },
Separate files
Instead of applying conditional checks using Platform.OS a number times throughout the entire
component file, we can also leverage the use of platform specific files instead. We can create
two separate files to represent the same component each with a different extension: .ios.js and
.android.js. If both files export the same component class name, the React Native packager knows
to choose the right file based on the path extension. We’ll dive deeper into platform specific
differences later in this book.
Text input
We now have text fields that display the location, weather condition, and temperature. The next
thing we need to do is provide some sort of input to allow the user to search for a specific city.
Again, we’ll continue using hardcoded data for now. We’ll only begin using an API for real data
once we have all of our components in place.
React Native provides a built-in TextInput component that we can import into our component
that allows us to accept user input. Let’s include it within our View container underneath the Text
components (make sure to import it as well!):
Getting Started with React Native 29
weather/2/App.js
<Text style={[styles.largeText, styles.textStyle]}>San Francisco</Text>
<Text style={[styles.smallText, styles.textStyle]}>Light Cloud</Text>
<Text style={[styles.largeText, styles.textStyle]}>24°</Text>
<TextInput
autoCorrect={false}
placeholder="Search any city"
placeholderTextColor="white"
style={styles.textInput}
clearButtonMode="always"
/>
There are a number of props associated with TextInput that we can use. We’ll cover the basics
here but go into more detail about them in the “Core Components” chapter. Here we’re specifying
a placeholder, its color, as well as a style for the component itself. Let’s create its style object,
textInput, underneath our other styles:
weather/2/App.js
smallText: {
fontSize: 18,
},
textInput: {
backgroundColor: '#666',
color: 'white',
height: 40,
width: 300,
marginTop: 20,
marginHorizontal: 20,
paddingHorizontal: 10,
alignSelf: 'center',
},
As we mentioned previously, all the attributes that we provide styles with in React Native are
extremely similar to how we would apply them using CSS. Now let’s take a look at our application:
Getting Started with React Native 30
Text Input
We can see that the text input has a default underline on Android. We’ll go over how to remove this
in a bit.
We’ve also specified the clearButtonMode prop to be always. This shows a button on the right side
of the input field when characters are inserted that allows us to clear the text. This is only available
on iOS.
Text Input Clear Button
We can now type into the input field!
Getting Started with React Native 31
If you’re using the iOS simulator, you can connect your hardware keyboard and use that with
any input field. This can be done with Shift + ⌘ + K or going to Hardware -> Keyboard ->
Connect Hardware Keyboard
With this enabled, the software keyboard may not show by default. You can toggle this by
pressing ⌘ + K or going to Hardware -> Keyboard -> Toggle Software Keyboard
Now every time you click an input field, the software keyboard will display exactly how it
would if you were using a real device and you can type using your hardware keyboard.
However one thing you may have noticed is that when you focus on the input field with a tap, the
keyboard pops up and covers it on Android and comes quite close on iOS:
Keyboard
Since the virtual keyboard can cover roughly half the device screen, this is a common prob-
lem that occurs when using text inputs in an application. Fortunately, React Native includes
KeyboardAvoidingView, a component that solves this problem by allowing us to adjust where other
components render in relation to the virtual keyboard. Let’s import and use this component instead
of View:
Getting Started with React Native 32
weather/2/App.js
render() {
return (
<KeyboardAvoidingView style={styles.container} behavior="padding">
<Text style={[styles.largeText, styles.textStyle]}>San Francisco</Text>
<Text style={[styles.smallText, styles.textStyle]}>Light Cloud</Text>
<Text style={[styles.largeText, styles.textStyle]}>24°</Text>
<TextInput
autoCorrect={false}
placeholder="Search any city"
placeholderTextColor="white"
style={styles.textInput}
clearButtonMode="always"
/>
</KeyboardAvoidingView>
);
}
Notice that KeyboardAvoidingView accepts a behavior prop with which we can customize how the
keyboard adjusts. It can change its height, position or bottom padding in relation to the position of
the virtual keyboard. Here, we’ve specified padding.
Now tapping the text input will shift our component text and input fields out of the way of the
software keyboard.
Getting Started with React Native 33
Keyboard Avoiding View
Custom components
So far, we’ve explored how to add styling into our application, and we’ve included some built-in
components into our main App component. We use View as our component container and import
Text and TextInput components in order to display hardcoded weather data as well as an input
field for the user to change locations.
It’s important to re-iterate that React Native is component-driven. We’re already representing our
application in terms of components that describe different parts of our UI without too much effort,
and this is because React Native provides a number of different built-in components that you can
use immediately to shape and structure your application.
However, as our application begins to grow, it’s important to begin thinking of how it can further
be broken down into smaller and simpler chunks. We can do this by creating custom components
that contain a small subset of our UI that we feel fits better into a separate, distinct component
file. This is useful in order to allow us to further split parts of our application into something more
manageable, reusable and testable.
Although our application in its current state isn’t extremely large or unmanageable, there’s still some
room for improvement. The first way we can refactor our component is to move our TextInput into
Getting Started with React Native 34
a separate component to hide its implementation details from the main App component. Let’s create
a components directory in the root of the application with the following file:
├── components/
- SearchInput.js
All the custom components we create that we use in our main App component will live inside this
directory. For more advanced apps, we might create directories within components to categorize
them more specifically. Since this app is pretty simple, let’s use a flat components directory.
The SearchInput will be our first custom component so let’s move all of our code for TextInput
from App.js to SearchInput.js:
weather/3/components/SearchInput.js
1 import React from 'react';
2 import { StyleSheet, TextInput, View } from 'react-native';
3
4 export default class SearchInput extends React.Component {
5 render() {
6 return (
7 <View style={styles.container}>
8 <TextInput
9 autoCorrect={false}
10 placeholder={this.props.placeholder}
11 placeholderTextColor="white"
12 underlineColorAndroid="transparent"
13 style={styles.textInput}
14 clearButtonMode="always"
15 />
16 </View>
17 );
18 }
19 }
20
21 const styles = StyleSheet.create({
22 container: {
23 height: 40,
24 marginTop: 20,
25 backgroundColor: '#666',
26 marginHorizontal: 40,
27 paddingHorizontal: 10,
28 borderRadius: 5,
29 },
Getting Started with React Native 35
30 textInput: {
31 flex: 1,
32 color: 'white',
33 },
34 });
Let’s break down what this file contains:
We export a component named SearchInput.
This component accepts a placeholder prop.
This component returns a React Native TextInput with a few of its properties specified
wrapped within a View. We’ve applied the appropriate styles to our view container including
a borderRadius. We also added underlineColorAndroid="transparent" to remove the dark
underline that shows by default on Android.
this is a special keyword in JavaScript. The details about this are a bit nuanced, but for the
purposes of the majority of this book, this will be bound to the React Native component
class. So, when we write this.props inside the component, we’re accessing the props
property on the component. When we diverge from this rule in later sections, we’ll point it
out.
For more details on this, check out this page on MDN
35
.
Custom props
As you may recall, in App.js we set the placeholder prop for TextInput to “Search any city. That
renders the text input with a placeholder:
For SearchInput, we could hardcode a string again for placeholder. But what if we wanted to add
a search input elsewhere in our application? It would be nice if placeholder was customizable.
Earlier in this chapter, we explored how we can use props with a number of built-in components in
order to customize their features. We can also create props for custom components that we build as
well.
That’s what we do here in SearchInput. The component accepts the prop placeholder. In turn,
SearchInput uses this value to set the placeholder prop on TextInput.
35
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
Getting Started with React Native 36
The way data flows from parent to child in React Native is through props. When a parent renders
a child, it can send along props the child depends on. A component can access all its props through
the object this.props. If we decide to pass down the string "Type Here" as the placeholder prop,
the this.props object will look like this:
{ "placeholder": "Type Here" }
In here, we’ll set up App to render SearchInput which means that App is the parent of SearchInput.
Our parent component will be responsible for passing down the actual value of placeholder.
We’re getting somewhere interesting now. We’ve set up a custom SearchInput component and by
building it to accept a placeholder prop, we’re already setting it up to be configurable. Based on
what it receives, it can render any placeholder message that we’d like.
Importing components
In order to use SearchInput in App, we need to import the component first. We can remove the
TextInput logic from App.js and have App use SearchInput instead:
weather/3/App.js
import React from 'react';
import { StyleSheet, Text, KeyboardAvoidingView, Platform } from 'react-native';
import SearchInput from './components/SearchInput';
export default class App extends React.Component {
render() {
return (
<KeyboardAvoidingView style={styles.container} behavior="padding">
<Text style={[styles.largeText, styles.textStyle]}>San Francisco</Text>
<Text style={[styles.smallText, styles.textStyle]}>Light Cloud</Text>
<Text style={[styles.largeText, styles.textStyle]}>24°</Text>
<SearchInput placeholder="Search any city" />
</KeyboardAvoidingView>
);
}
}
By moving the entire TextInput details into a separate component called SearchInput, we’ve
made sure to not have any of its specific implementation details showing in the parent component
anymore. We can also remove the text input’s styling defined within the styles object.
Getting Started with React Native 37
There’s no specific answer to how often we should isolate different UI logic into separate custom
components. React Native was built in order to allow us to lay out our entire application in terms
of self-contained components, and that means we should separate parts of our application into
distinct units with custom functionality attached to them. This allows us to build a more manageable
application that’s easier to control and understand. We’ve isolated knowledge of our search input to
the component SearchInput and we’ll continue to isolate specific pieces of our app throughout this
chapter.
It’s common to separate your imports into two groups: imports from dependencies, and
imports from other files in your project. That’s why we put a blank line above SearchInput.
This comes down to personal style preference.
Background image
As we saw in the photo of the completed version of the app at the beginning of this chapter, we can
make our application more visually appealing by displaying a background image that represents the
current weather condition.
In this book’s sample code, we’ve included a number of images for various weather conditions. If you
inspect the weather/assets directory, you’ll find images like clear.png, hail.png, and showers.png.
If you’re following along, copy these two folders over from the sample code into your project:
1. weather/assets
2. weather/utils
We mentioned earlier that we’ve included a utils/ folder for each project in the book’s
sample code. This folder contains helper functions that we’ll use below.
If you’re on macOS or Linux, you can use cp -r to copy directories:
cp -r weather/{assets,utils} ~/react-native-projects/weather/
With the assets and utils folders copied over, let’s update our App component:
Getting Started with React Native 38
weather/4/App.js
import React from 'react';
import {
StyleSheet,
View,
ImageBackground,
Text,
KeyboardAvoidingView,
Platform,
} from 'react-native';
import getImageForWeather from './utils/getImageForWeather';
import SearchInput from './components/SearchInput';
export default class App extends React.Component {
render() {
return (
<KeyboardAvoidingView style={styles.container} behavior="padding">
<ImageBackground
source={getImageForWeather('Clear')}
style={styles.imageContainer}
imageStyle={styles.image}
>
<View style={styles.detailsContainer}>
<Text style={[styles.largeText, styles.textStyle]}>
San Francisco
</Text>
<Text style={[styles.smallText, styles.textStyle]}>
Light Cloud
</Text>
<Text style={[styles.largeText, styles.textStyle]}>24°</Text>
<SearchInput placeholder="Search any city" />
</View>
</ImageBackground>
</KeyboardAvoidingView>
);
}
}
In this component, we’re importing a getImageForWeather method from our utils directory which
Getting Started with React Native 39
returns a specific image from the assets directory depending on a weather type. For example,
getImageForWeather('Clear') returns the following image:
Feel free to peek into the implementation details of any function we use from the utils
directory to get a better idea of how it works.
We also import React Native’s built-in ImageBackground component. Let’s take a closer look at how
we’re making use of it in our render method:
weather/4/App.js
render() {
return (
<KeyboardAvoidingView style={styles.container} behavior="padding">
<ImageBackground
source={getImageForWeather('Clear')}
style={styles.imageContainer}
imageStyle={styles.image}
>
Conceptually, the ImageBackground component is a View with an Image nested within. The source
prop accepts an image location, which we’ve set to getImageForWeather('Clear'). We know this
Getting Started with React Native 40
will always return the image displayed above. ImageBackground also uses the prop style for styling
the View container and the prop imageStyle for styling the image itself. Let’s add two new styles
and modify the container style:
weather/4/App.js
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#34495E',
},
imageContainer: {
flex: 1,
},
image: {
flex: 1,
width: null,
height: null,
resizeMode: 'cover',
},
Defining component styles with a flex attribute mean that they will expand to take up any room
remaining in their parent container in relation to any sibling components. They share this space in
proportion to their defined flex values. Since ImageBackground is the only nested element within
KeyboardAvoidingView, setting imageContainer to flex: 1 means that this element will fill up
the entire space of its parent component. We’ve removed justifyContent and alignItems from
container so that the ImageBackground can take up the entire device screen.
We also used flex: 1 to style the actual image itself, image, to make sure it takes up the entire
space of its parent container. With images in particular, the component will fetch and use the actual
width and height of the source image by default. For this reason, we’ve also set its height and width
attribues to null so that the dimensions of the image fit the container instead. The resizeMode
attribute allows us to define how the image is resized when the Image element does not match its
actual dimensions. Setting this attribute to cover means that the image will scale uniformly until it
is equal to the size of the component.
The “Core Components” chapter will dive deeper into how flexbox, layout, and the Image
component work in React Native
We also wrapped all of our Text elements and SearchInput within a view container styled with
detailsContainer:
Getting Started with React Native 41
weather/4/App.js
<View style={styles.detailsContainer}>
<Text style={[styles.largeText, styles.textStyle]}>
San Francisco
</Text>
<Text style={[styles.smallText, styles.textStyle]}>
Light Cloud
</Text>
<Text style={[styles.largeText, styles.textStyle]}>24°</Text>
<SearchInput placeholder="Search any city" />
</View>
Now let’s set up its style:
weather/4/App.js
detailsContainer: {
flex: 1,
justifyContent: 'center',
backgroundColor: 'rgba(0,0,0,0.2)',
paddingHorizontal: 20,
},
Here, we’re ensuring the container within ImageBackground also fills up the entire space of its
parent component as well as have its items aligned at the center of the screen. We also add a semi-
transparent overlay to our image by setting the backgroundColor of this component.
The last thing we’ll need to do here is change our Text elements to white instead of black to show
more clearly with a background image:
weather/4/App.js
textStyle: {
textAlign: 'center',
fontFamily: Platform.OS === 'ios' ? 'AvenirNext-Regular' : 'Roboto',
color: 'white',
},
Try it out
Save the file and take a look at our app. We should now see the background image displayed!
Getting Started with React Native 42
Modifying location
The steps we’ve taken so far are quite common when starting React Native applications. We hardcode
all our data, organize our app into components, and get an idea of the visual layout as well as how
it breaks down into components.
However, our app really isn’t very useful at this moment. If we take a look at our SearchInput
component for instance, we can type anything into the input field but nothing actually happens as
a result. We need to find a way to track changes made to the component and store that information
somewhere. In other words, we need some piece of mutable data that updates whenever the user
changes or submits the input field.
Instead of having SearchInput not actually manage any data that represents the text inputted by
the user, let’s pass in a prop for it called location to reflect what the user has inputted into the text
input field:
render() {
const location = 'San Francisco';
return (
<KeyboardAvoidingView style={styles.container} behavior="padding">
<ImageBackground
source={getImageForWeather('Clear')}
style={styles.imageContainer}
imageStyle={styles.image}
>
<View style={styles.detailsContainer}>
<Text style={[styles.largeText, styles.textStyle]}>{location}</Text>
<Text style={[styles.smallText, styles.textStyle]}>
Light Cloud
</Text>
<Text style={[styles.largeText, styles.textStyle]}>24°</Text>
<SearchInput placeholder="Search any city" />
</View>
</ImageBackground>
</KeyboardAvoidingView>
The reason we want to pass in the property that contains our location data is we need a way for our
child component to modify that field and communicate back up to our container App component.
Notice how we’ve moved the static string for location into a separate constant which we pass down
to SearchInput. We’ve instantiated it as San Francisco so that it can show as the first location when
the user loads the application. The next thing we just need to do is make sure that this location
constant is updated when the user actually changes the field in SearchInput:
Getting Started with React Native 43
export default class SearchInput extends React.Component {
handleChangeText(newLocation) {
// We need to do something with newLocation
}
render() {
return (
<TextInput
autoCorrect={false}
placeholder={this.props.placeholder}
placeholderTextColor="white"
underlineColorAndroid="transparent"
style={styles.textInput}
clearButtonMode="always"
onChangeText={this.handleChangeText}
/>
);
}
}
So what did we just do? We’ve just added onChangeText as a new prop to our TextInput component.
Notice that we don’t pass in a specific object or property, but a function instead:
onChangeText={this.handleChangeText}
This method is invoked everytime the text within the input field is changed. A number of built-
in components provided by React Native include event-driven props which we can attach specific
methods to. We’ll explore more throughout this book.
With onChangeText, our TextInput returns the changed text as an argument which we’re attempting
to pass into a separate method called handleChangeText. Currently our method is blank and we’ll
explore how we can complete it in a bit.
In React Native, we need to pass in functions when we want to handle certain events related to the
component being referenced. For the TextInput component, onChangeText is set to fire every single
time the text within the input field has changed. We need to “listen” to this specific event in our child
component (TextInput) so that it can notify our parent component (SearchInput) to respond to this
event. To do this, we pass in a function that calls another function, or in other words, a callback.
This is a common pattern when building components which need to notify a parent component of
some event. Unfortunately with the way we’ve just set it up, it wouldn’t work in this example. This
is because the function handleChangeText has a different local scope than the component instance.
We can work around this by binding our function to the correct context of its this object.
Getting Started with React Native 44
<TextInput
placeholder={placeholder}
placeholderTextColor="white"
underlineColorAndroid="transparent"
style={styles.textInput}
clearButtonMode="always"
onChangeText={this.handleChangeText.bind(this)}
/>
Now this might seem okay for the current context, but it can quickly become unwieldy if we build
our components with bind statements in each event handler. One reason why is if we wanted to use
handleChangeText in multiple different sub-components for example, we would have to make sure
to bind it to the correct context every single time. To help solve this, we can take care of handling
our event using property initializers:
export default class SearchInput extends React.Component {
handleChangeText = (newLocation) => {
// We need to do something with newLocation
}
render() {
return (
<TextInput
autoCorrect={false}
placeholder={this.props.placeholder}
placeholderTextColor="white"
underlineColorAndroid="transparent"
style={styles.textInput}
clearButtonMode="always"
onChangeText={this.handleChangeText}
/>
);
}
}
This allows us to declare the member methods as arrow functions:
handleChangeText = (newLocation) => {
// We need to do something with newLocation
}
And we pass the method name to the prop and nothing more:
Getting Started with React Native 45
onChangeText={this.handleChangeText}
Property Initializers
Supported by Babel
36
, property initializers are still in the proposal phase and have not yet
been slated for adoption in future JavaScript versions. Although this pattern is used quite
often in many React and React Native applications, it is important to keep in mind that it is
still experimental syntax.
For more information on the different ways to handle events in React Native, refer to the
Appendix.
Now that we’ve set up our callback correctly, let’s modify handleChangeText to change our text
prop in order to change the data to match what the user is typing:
handleChangeText = (newLocation) => {
this.props.location = newLocation;
};
Let’s run our application and try typing into the TextInput field. You’ll immediately notice that the
first location that shows is San Francisco, so we know that the text prop is being passed down
successfully!
However, if we type anything into our TextInput, you’ll notice nothing happens. Changing the text
within the input field does not actually update the parent location property and from the way
we’ve designed our component logic, it looks like it should. This is because this.props, which is
referenced in SearchInput, is actually owned by App and not the child component, SearchInput.
A component’s props are immutable and create a one-way data pipeline from parent to children.
We have a bit of a problem. We need to find a way to:
store local data in our child component, SearchInput, that represents the value in the input
field
track changes to the search input field as it’s updated by the user
notify our parent component, App, whenever our location changes
This is where we can use a component’s state.
Storing local data
Let’s modify our SearchInput component once more. Currently the text input within the component
does nothing, so let’s add some local component state to control actual data. We can do this by adding
a constructor method to the component. We can then initialize the component’s state within this
method:
36
https://babeljs.io/docs/plugins/transform-class-properties/
Getting Started with React Native 46
weather/5/components/SearchInput.js
export default class SearchInput extends React.Component {
constructor(props) {
super(props);
this.state = {
text: '',
};
}
We can use the constructor method to to initialize our component-specific data, or state. We
do this here because this method fires before our component is mounted and rendered. Here, we
defined our state object to only contain a text property:
Remember, components in React Native are extended from
React.Component
to create
derived classes. super() is required in derived classes in order to reference this within
the constructor.
Much like how we can access the component’s props with this.props, we can access the compo-
nent’s state via this.state. For example if we wanted to output our state property in a single Text
component, we could do this:
export default class HiThere extends React.Component {
constructor(props) {
super(props);
this.state = {
text: 'Hi there!',
};
}
render() {
return <Text>{this.state.text}</Text>;
}
}
This component would now render 'Hi there!' since that’s how we defined our state.text
property in our constructor. For our current component however, our text property in state will
be used to define the text typed by the user into the input field. Let’s now modify our component’s
render method to allow for this:
Getting Started with React Native 47
weather/5/components/SearchInput.js
render() {
const { placeholder } = this.props;
const { text } = this.state;
return (
<View style={styles.container}>
<TextInput
autoCorrect={false}
value={text}
placeholder={placeholder}
placeholderTextColor="white"
underlineColorAndroid="transparent"
style={styles.textInput}
clearButtonMode="always"
onChangeText={this.handleChangeText}
onSubmitEditing={this.handleSubmitEditing}
/>
</View>
);
}
The first thing we did was destructure the component props and state objects:
weather/5/components/SearchInput.js
render() {
const { placeholder } = this.props;
const { text } = this.state;
Destructuring
Instead of using this.props.placeholder and this.state.text directy, we destructured
both objects at the beginning of our render method into individual variables (text and
placeholder). Please refer to the Appendix for more details on destructuring assignments.
We then make sure that the TextInput placholder prop is still accepting our props.placeholder
attribute. We also pass state.text to a value prop:
Getting Started with React Native 48
weather/5/components/SearchInput.js
<TextInput
autoCorrect={false}
value={text}
placeholder={placeholder}
placeholderTextColor="white"
underlineColorAndroid="transparent"
style={styles.textInput}
clearButtonMode="always"
onChangeText={this.handleChangeText}
onSubmitEditing={this.handleSubmitEditing}
/>
The value prop is responsible for the content showed in the input field. With this, we now know
whatever is displayed in input field will always represent our local state.
We’ve also attached two additional props to our component, onChangeText and onSubmitEditing
with methods we haven’t set up yet.
Tracking changes to input
Let’s take a look at how
onChangeText
can allow us to update our
state
every time the input field is
changed. As we just did previously, we’re attaching a method to the onChangeText prop of TextInput:
weather/5/components/SearchInput.js
onChangeText={this.handleChangeText}
Previously, we set up a handleChangeText method that modifies our location prop value when the
user changes the text within the input. We quickly realized that this didn’t work. This is because
props are immutable and are always “owned” by a component’s parent while state can be mutated
and is “owned” by the component itself. This is an extremely important pattern to remember while
building components with React Native.
This brings us to setState(), a method we can use to change our state correctly. Let’s make use of
this in our handleChangeText method which we can declare right underneath our constructor:
Getting Started with React Native 49
weather/5/components/SearchInput.js
export default class SearchInput extends React.Component {
constructor(props) {
super(props);
this.state = {
text: '',
};
}
handleChangeText = text => {
this.setState({ text });
};
Shorthand property names
With later versions of JavaScript, we can define objects using shorthand form where possible.
Our handleChangeText method can also be written in a more explicit syntax:
handleChangeText = (text) => {
this.setState({ text: text });
};
Please refer to the Appendix for a little more detail on this concept.
Now we might be tempted to update our state by using this.state.text = text, but this will
not work. For all state modifications after the initial state we’ve defined in our constructor,
React provides components with the method setState() to do this. In addition to mutating the
component’s state object, this method triggers the React component to re-render, which is essential
after the state changes.
It’s good practice to initialize components with empty” state as we’ve done in this component.
However, after our SearchInput component is initialized, we want to update the state for with data
the user types into the text input. This is why we use the text argument provided into our callback
method as part of the onChangeText prop and pass that into this.setState().
Never modify state outside of this.setState(). This function has important hooks around
state modification that we would be bypassing.
We discuss state management in detail throughout the book.
Getting Started with React Native 50
Notifying the parent component
So we’ve found a way to correctly store local state in our component that represents the text within
the search input and make sure that it updates as the user changes the value. We still need to do
one more thing which is to notify our parent App component when the user submits a new searched
value. This is why we’ve attached a method to the onSubmitEditing prop of TextInput:
weather/5/components/SearchInput.js
onSubmitEditing={this.handleSubmitEditing}
The idea here is we don’t necessarily want to communicate with our parent component everytime
the user changes the input field. That’s why onChangeText is purely responsible for storing the latest
typed input value into the local state of the component. Fortunately, the TextInput component has
an onSubmitEditing prop which fires when the user submits the field and not just changes it. This
happens specifically when the user presses the action button of the virtual keyboard in order to
submit their input. This is where we would want to notify our container component of the typed
user data. Let’s take a look at how we can set up the handleSubmitEditing function that we’re
passing in:
weather/5/components/SearchInput.js
handleSubmitEditing = () => {
const { onSubmit } = this.props;
const { text } = this.state;
if (!text) return;
onSubmit(text);
this.setState({ text: '' });
};
In here, we check if this.state.text is not blank (which means the user has typed something into
the field), and if that’s the case:
1. Run an onSubmit function obtained from the component’s props. We pass text as an
argument here.
2. Clear the text property in state using this.setState()
We’ve seen how this.props can be used to pass information down from a parent component to child
and we’ve also seen how built-in components such as TextInput can notify their parent component
through callbacks in some of their props. Similarly, we can create props in our custom components
to do the exact same thing. In here, we need SearchInput to communicate with the App component
Getting Started with React Native 51
whenever the user submits the input field. We do this because we want our parent component to
handle the event of the user typing and submitting a new city. This is why we have an onSubmit
prop here that gets fired.
The next thing we need to do is pass a method to the onSubmit prop of SearchInput in App and
handle the event:
weather/5/App.js
<SearchInput
placeholder="Search any city"
onSubmit={this.handleUpdateLocation}
/>
Let’s define local state for this component as well as the handleUpdateLocation method:
weather/5/App.js
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
location: 'San Francisco',
};
}
handleUpdateLocation = city => {
this.setState({
location: city,
});
};
We defined local state for this component with just a location property and have it set to San
Francisco. We do this to ensure that an initial location is shown when we reload our application.
We also included a handleUpdateLocation method that takes in a parameter to change our location
state. This method will fire everytime the user submits the search input field because we pass this
method as the onSubmit prop for SearchInput.
Since we actually have “living” location data represented by what the user submits in the input field,
we can now display it in our first Text element instead of a hardcoded string:
Getting Started with React Native 52
weather/5/App.js
render() {
const { location } = this.state;
return (
<KeyboardAvoidingView style={styles.container} behavior="padding">
<ImageBackground
source={getImageForWeather('Clear')}
style={styles.imageContainer}
imageStyle={styles.image}
>
<View style={styles.detailsContainer}>
<Text style={[styles.largeText, styles.textStyle]}>{location}</Text>
Try it out
If we type any city in the search input and press return, we’ll see the name of the city being displayed
immediately.
Component State
Getting Started with React Native 53
This shows that we’ve wired everything correctly!
We’ve sequenced each of our problems step by step and showed how props and state differ when
trying to pass and store component data. However, handleUpdateLocation doesn’t really get real
weather information and just updates the city name that’s being displayed. We’ll wire it up to get
actual weather data soon.
Architecting state
We may have already considered controlling all the location state within SearchInput and not
having to deal with passing information upwards to a container component. There’s no specific
answer to where each piece of state should live and it depends on the type of application we’re
building. This is a core concept of building React Native applications and tools like Redux
37
and
MobX
38
aim to simplify this even further by allowing you to manage the entire state of the
application in a single location. However, even when we decide to use state management libraries
such as these examples, we still need to spend time deciding on how we want to structure our state
logic.
In our current app, we need to have App know the location data in order to display correct weather
conditions. SearchInput doesn’t really need to store this information without actually passing it up
to the component that handles the logic. The motivation behind keeping SearchInput simple is that
we can leverage React’s component-driven paradigm. We can re-use it in various places across our
application whenever we need a search input.
We can think of SearchInput as a component that provides presentational markup and does not
manage any real application data. Such components accept props from parent components which
specify the data a presentational component should render. This parent container component also
specifies behavior. If the lower level presentational component has any interactivity like our
search input it calls a prop-function given to it by the parent. We’ll go into more detail about
this important pattern throughout this book.
Lifecycle methods
We’ve wired up how our components communicate with each other to have a new location displayed
immediately when the user submits the text input field. However, you’ll notice that the city shows a
blank string when the app first loads. We could instantiate it with the name of an actual city instead
but we know we want to be getting actual weather information eventually. Although we haven’t set
that up just yet, the asynchronous action to fetch actual weather data for a city will be happening in
the handleUpdateLocation. Therefore it makes sense to call this method when our component first
loads. One thing we might be tempted to try is firing this method in our constructor:
37
https://github.com/reactjs/redux
38
https://github.com/mobxjs/mobx
Getting Started with React Native 54
constructor(props) {
super(props);
this.state = {
location: '',
};
this.handleUpdateLocation('San Francisco');
}
However, firing off asynchronous requests in the constructor is typically an anti-pattern. This is
because the constructor is called before the component is first mounted. As such, this method
should usually only be used to initialize state and bind methods.
Instead, we can make use of one of React Native’s lifecycle methods. Like the name suggests, these
methods allow you to access specific points in the lifecycle of a component. The term lifecycle here
applies to how React Native instantiates, changes and destroys components. We can use lifecycle
hooks to do something when these functions are called during different phases of component
rendering.
The most common lifecycle method used is the one that allows us to set component data after the
component is mounted componentDidMount(). This method is commonly used to trigger network
requests to fetch data that the component would need. To understand when this method fires, let’s
add it to our component right after our constructor with a console.log:
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
location: 'San Francisco',
};
}
componentDidMount() {
console.log('Component has mounted!');
}
When we reload our application, we can see Component has mounted! outputted directly to our
terminal as soon as the component has mounted.
Getting Started with React Native 55
Debugging in React Native
If you’ve worked with JavaScript on the web, you may be familiar with using console.log,
console.warn or console.error to output messages to the browser’s console for debugging
purposes. Similarly, Expo allows us to use these methods to output logs to our terminal. For
more detail about viewing logs, you can refer to the documentation
39
.
Aside from logging, React Native also allows us to debug the JavaScript code in our app
using the Chrome Developer Tools. With Expo, we can do this by pressing Debug Remote JS
in the developer menu. You can refer to the documentation
40
to learn more.
Now let’s update it to fire handleUpdateLocation:
weather/6/App.js
componentDidMount() {
this.handleUpdateLocation('San Francisco');
}
With this, we can remove San Francisco as our default location in state and set it to an empty
string.
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
location: '',
};
}
Since we’re using componentDidMount, we should still see San Francisco populated in place of the
text field as soon as we reload the app.
Although componentDidMount() allows us to create event listeners and fetch network
requests right after the component has rendered for the first time, there are number of other
lifecycle methods that React Native provides. We’ll go through each of them throughout this
book.
39
https://docs.expo.io/versions/latest/guides/logging.html
40
https://docs.expo.io/versions/latest/guides/debugging.html
Getting Started with React Native 56
Networking
We’ve built all the components that make up the UI of our app and refined it to show a nice
background image for the user. As we mentioned previously, the approach we’ve taken so far is
a common pattern used when building brand new React Native applications. We first organized our
views using components and then introduced some state and state management.
However, nobody will find our app useful unless it’s actually connected to real data. When building
a new mobile app, chances are we’ll need to communicate with a server. Communicating with a
server is a crucial component of most mobile applications.
For the purpose of this application, we’ll use the MetaWeather
41
API to fetch real weather
information. MetaWeather is a weather data aggregator that calculates the most likely outcome
from predictions of different forecasters. They provide an API
42
that provides this information over
a set of different endpoints:
1. Location search (/api/location/search/) which allows us to search for a particular city
2. Location weather information (/api/location/{woeid}) which provides a 5 day forecast for a
certain location
3. Location day which provides (/api/location/{woeid}/{date}/) forecast history and informa-
tion for a particular day and location
WOEID, or Where On Earth ID
43
, is a location identifier that allows us find details about a
specific location. For more detail on how exactly the MetaWeather API works, feel free to
take a closer look at the documentation
44
.
Now that we have a basic understanding of how state and props control the flow of data between
different components, let’s move on to using this API to render real weather data. It’s possible to
put API calls directly in our component methods, but it’s usually a good idea to abstract that logic
away in its own file. In the utils directory, we’ve set up two separate API calls in api.js:
fetchLocationId returns an array of locations based on a search query
fetchWeather returns weather details about a specific location using a location identifier
known as Where On Earth ID
45
The combination of both calls will allow us to search for a city and retrieve its weather information.
Feel free to open the file and take a look at how these methods work if you’re interested.
41
https://www.metaweather.com/
42
https://www.metaweather.com/api/
43
https://developer.yahoo.com/geo/geoplanet/guide/concepts.html
44
https://www.metaweather.com/api/
45
https://developer.yahoo.com/geo/geoplanet/guide/concepts.html
Getting Started with React Native 57
Async Functions
Callbacks and Promises are two ways to define asynchronous code in JavaScript. Built on
top of promises, async functions are a newer syntax that allows us to define asynchronous
methods in a synchronous manner. Both methods we’ve set up in api.js use this syntax.
Although supported by Babel, it is still in draft proposal stage and will most likely be ratified
into a future JavaScript release. Here’s the MDN
46
resource if you happen to be interested
in learning more about this syntax further.
When building components that fetch information over the network, it’s inevitable that the user will
have to wait a certain period of time before the data is retreived. With most applications, it makes
sense to show a loading indicator of some sort so the user knows they have to wait a bit before they
can see the content. Fortunately, React Native provides a built-in ActivityIndicator component
that displays a circular loading spinner. Let’s update our root App component beginning with some
new imports:
weather/6/App.js
import React from 'react';
import {
StyleSheet,
View,
ImageBackground,
Text,
KeyboardAvoidingView,
Platform,
ActivityIndicator,
StatusBar,
} from 'react-native';
import { fetchLocationId, fetchWeather } from './utils/api';
import getImageForWeather from './utils/getImageForWeather';
import SearchInput from './components/SearchInput';
We’ve added the following imports:
ActivityIndicator is a built-in component that displays a circular loading spinner. We’ll use
it when data is being fetched from the network
fetchLocationId, fetchWeather are the methods for interacting with the weather API
StatusBar is a built-in component that allows us to modify the app status bar at the top of the
device
46
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Getting Started with React Native 58
Now let’s make some changes to our component. We need to apply our network request logic and
store that information so that it can be easily displayed. We also need to make sure that a loading
indicator is shown while the request is firing. Let’s begin with updating our state:
weather/6/App.js
constructor(props) {
super(props);
this.state = {
loading: false,
error: false,
location: '',
temperature: 0,
weather: '',
};
}
We just expanded our state object to include loading, error, temperature, and weather in addition
to location. The three latter properties are data we’ll retreive from the API. The loading property
represents when a call is still being made (in order to show a loading icon) and error is used to store
the error message if our call fails or returns unusable information.
With setState, updates to our state can happen asynchronously. For this reason, the method accepts
a callback as an optional second parameter that allows us to define an action to fire after the state
is updated. Consider the following as an example:
export default class Example extends React.Component {
state = {
weather: '',
};
componentDidMount() {
this.setState({ weather: 'Clear' }, () => console.log(this.state));
}
}
// { weather: 'Clear' } is logged right after our component finishes mounting
We can apply this logic to the method responsible for interfacing with our external API: handleUpdateLocation:
Getting Started with React Native 59
weather/6/App.js
handleUpdateLocation = async city => {
if (!city) return;
this.setState({ loading: true }, async () => {
try {
const locationId = await fetchLocationId(city);
const { location, weather, temperature } = await fetchWeather(
locationId,
);
this.setState({
loading: false,
error: false,
location,
weather,
temperature,
});
} catch (e) {
this.setState({
loading: false,
error: true,
});
}
});
};
We’ve updated it to be an asynchronous function that uses setState to change our loading attribute
to true. We also pass in an asynchronous function as its second argument. In here, we first call
fetchLocationId with the user queried city (if present) and pass the location ID to fetchWeather to
return an object that contains the required information (location, weather, and temperature). Once
complete, our state is updated with the correct parameters. Moreover, if any of the calls happen to
error, the catch statement will update the error property in our state to true.
Now that we have our API logic in place, we’ll need to do a few things in the UI of our component:
We need to display a loading spinner only when our API calls have fired but not completed
We should show an error message if the user types in an incorrect address or our API call fails
We need to render the correct weather information for a certain location
Let’s take a look at how we can update our render() method to do this:
Getting Started with React Native 60
weather/6/App.js
render() {
const { loading, error, location, weather, temperature } = this.state;
return (
<KeyboardAvoidingView style={styles.container} behavior="padding">
<StatusBar barStyle="light-content" />
<ImageBackground
source={getImageForWeather(weather)}
style={styles.imageContainer}
imageStyle={styles.image}
>
<View style={styles.detailsContainer}>
<ActivityIndicator animating={loading} color="white" size="large" />
{!loading && (
<View>
{error && (
<Text style={[styles.smallText, styles.textStyle]}>
Could not load weather, please try a different city.
</Text>
)}
{!error && (
<View>
<Text style={[styles.largeText, styles.textStyle]}>
{location}
</Text>
<Text style={[styles.smallText, styles.textStyle]}>
{weather}
</Text>
<Text style={[styles.largeText, styles.textStyle]}>
{`${Math.round(temperature)}°`}
</Text>
</View>
)}
<SearchInput
placeholder="Search any city"
onSubmit={this.handleUpdateLocation}
/>
</View>
)}
Getting Started with React Native 61
</View>
</ImageBackground>
</KeyboardAvoidingView>
);
}
It might look like a lot is going on in the file, but let’s break it down piece by piece. We first included
our StatusBar component:
weather/6/App.js
<StatusBar barStyle="light-content" />
The StatusBar component allows us to customize the status bar of our application using a barStyle
prop that lets us change the color of the text within the bar. A value of light-content renders a
lighter color (white) and dark-content will change it to a darker color (dark-grey).
With Expo, we can also configure the status bar for Android by modifying app.json.
Expo defaults barStyle for Android to light-content and makes the background translu-
cent. Although this looks fine for our current application, you can remove the translucency
by providing a background color. Take a look at the documentation
47
for more details.
We then added ActivityIndicator along with assigning its color and size prop:
weather/6/App.js
<ActivityIndicator animating={loading} color="white" size="large" />
Notice how we’ve also included an animating prop which we’ve set to be our state.loading
attribute. This prop is responsible for showing or hiding the component entirely.
After that, we’ve included a curly brace container in our JSX:
47
https://docs.expo.io/versions/latest/guides/configuring-statusbar.html
Getting Started with React Native 62
weather/6/App.js
{!loading && (
<View>
{error && (
<Text style={[styles.smallText, styles.textStyle]}>
Could not load weather, please try a different city.
</Text>
)}
{!error && (
<View>
<Text style={[styles.largeText, styles.textStyle]}>
{location}
</Text>
<Text style={[styles.smallText, styles.textStyle]}>
{weather}
</Text>
<Text style={[styles.largeText, styles.textStyle]}>
{`${Math.round(temperature)}°`}
</Text>
</View>
)}
<SearchInput
placeholder="Search any city"
onSubmit={this.handleUpdateLocation}
/>
</View>
)}
</View>
We’ve previously seen how JSX allows us to embed JavaScript expressions within curly braces.
Fortunately, this lets us include operators as well, allowing us to conditionally render certain parts
of our UI. In here, !loading && <...> means that this statement will evaluate and display the
element if and only if loading is false. We can see we’ve pretty much wrapped most of the elements
that make up our component within here, and this makes sense since we don’t want to show any
text fields or the search input while the API call is being fetched.
Getting Started with React Native 63
Conditional Rendering
Using logical && operators within the render method is not the only way to conditionally
render parts of the component. At times, this approach can make it harder to read a
component file if a significant number of lines are being conditionally rendered.
If this happens, it might be a good idea to use helper methods. For example, our render
method can be rewritten following this pattern:
renderContent() {
const { error } = this.state;
return (
<View>
{error && <Text>Error</Text>}
{!error && this.renderInfo()}
</View>
);
}
renderInfo() {
const { info } = this.state;
return <Text>{info}</Text>;
}
render() {
const { loading } = this.state;
return (
<View>
<ActivityIndicator animating={loading} color="white" size="large" />
{!loading && this.renderContent()}
</View>
);
}
The React documentation
48
goes into more detail about this concept as well as explaining
even more ways to conditionally render parts of components. Ultimately, it depends on
preference on which pattern to use.
Now within the content that shows when the API call isn’t being fired, we still need to be able to
display an appropriate error message if there’s an issue. We can use the state.error attribute to
conditionally display text in this scenario:
48
https://reactjs.org/docs/conditional-rendering.html
Getting Started with React Native 64
weather/6/App.js
{error && (
<Text style={[styles.smallText, styles.textStyle]}>
Could not load weather, please try a different city.
</Text>
)}
{!error && (
<View>
<Text style={[styles.largeText, styles.textStyle]}>
{location}
</Text>
<Text style={[styles.smallText, styles.textStyle]}>
{weather}
</Text>
<Text style={[styles.largeText, styles.textStyle]}>
{`${Math.round(temperature)}°`}
</Text>
</View>
)}
<SearchInput
placeholder="Search any city"
onSubmit={this.handleUpdateLocation}
/>
Notice how we now display our state information (location, weather, and temperature) in our
Text elements instead of hard-coded values. For temperature, we’re making use of the JavaScript
Math object and its round() method to round the temperature to the nearest integer.
The last thing we also do is pass the dynamic weather attribute to ImageBackground instead of a
hardcoded Clear string:
weather/6/App.js
<ImageBackground
source={getImageForWeather(weather)}
style={styles.imageContainer}
imageStyle={styles.image}
>
Now if we run our application, typing a city into the input field will return its actual weather data!
Getting Started with React Native 65
We’ve pretty much finished connecting all the major points of our application by wiring in network
requests to retreive actual data. After slowly beginning with hardcoded data and building our
components that make up the building blocks of our UI, our application now works just as we
intended from the beginning of this chapter. The next few sections will explore some additional
enhancements to our code but won’t add any new functionality to our app.
PropTypes
With React Native, we can include validation functions using the prop-types library. This allows
us to specify and enforce the type of our component props and ensure that match what we expect
them to be. This can not only help us catch development errors sooner but also provide a layer of
documentation to the consumer of our components
We can add prop-types as a dependency:
yarn add prop-types
Now let’s take a look at how we can use PropTypes in SearchInput:
Getting Started with React Native 66
weather/6/components/SearchInput.js
SearchInput.propTypes = {
onSubmit: PropTypes.func.isRequired,
placeholder: PropTypes.string,
};
SearchInput.defaultProps = {
placeholder: '',
};
We’ve defined a propTypes object which instructs React to validate the props given to our
component. We’re specifying that onSubmit must be a function and placeholder must be a string.
We’ve also specified onSubmit to be required which means it has to be provided to out component
and is not optional.
We’ve left placeholder to be optional. For this, we’re making use of the defaultProps object. This
allows us to create our component and not specify placeholder if we don’t need to, defaultProps
will take care of providing it’s value in that case. It’s important to note that the value passed into
defaultProps also undergoes type-checking as well by the library.
Now what exactly happens when a prop’s type is not validated successfully? When a prop is passed
in with an invalid type or fails the propType validation, a warning is passed into the JavaScript
console. These warnings will only be shown in development mode, so if we accidentally deploy our
app into production with an improper use of a component, our users won’t see the warning.
Class properties
React Native includes class properties transformation
49
from Babel that allows us to simplify how
we define our component state, props, and propTypes. For example, we can update the constructor
in App.js to:
weather/App.js
state = {
loading: false,
error: false,
location: '',
temperature: 0,
weather: '',
};
This gets transpiled into the exact same result as using a constructor. Similarly, we can simplify
how we define our state in SearchInput:
49
https://babeljs.io/docs/plugins/transform-class-properties/
Getting Started with React Native 67
weather/components/SearchInput.js
state = {
text: '',
};
Moreover, we can also set propTypes and defaultProps using static properties in our class. In other
words, we can remove the object references in SearchInput and define a static method within the
class:
weather/components/SearchInput.js
static propTypes = {
onSubmit: PropTypes.func.isRequired,
placeholder: PropTypes.string,
};
static defaultProps = {
placeholder: '',
};
Using this pattern and leveraging class properties transform is purely syntactical sugar over defining
methods and objects separately and allows us to write in a cleaner, simpler syntax.
Summary
Congratulations, we’ve just built our very first React Native application and covered almost all of
the essentials needed to build a complete and fully functional mobile app. We began by exploring
each of the files generated as a result of starting a new project with CRNA and how Expo allows us
to run our application smoothly on our device without worrying about Xcode and Android Studio
set up. We then built out each of the components that make up our application using the built-in
components provided by React Native. While doing so, we dove into the fundamentals of React
Native understanding JSX, how to apply custom styling as well as understanding how to use props
and state to manage and control data. We moved on to more complex topics including lifecycle
methods and how to use external network calls to provide real content to our application. Finally,
we finished off with a brief look into how propTypes can add an additional layer of safety by adding
type validation to our application. The rest of this book will dive deeper into core concepts of React
Native and the concepts learned in this chapter will serve as the foundation for everything else in
the text.
So far, we’ve only scratched the surface of what React Native allows us to do. By knowing the
setup/development details like Expo and the core concepts of props, state, and components, you
already have the essentials of React Native development under your belt. As of now, you can already
build a wide variety of applications using the framework so go forth and build something amazing!
React Fundamentals
In the last chapter, we built our first React Native application. We explored how React applications
are organized by components. Using the key React concepts of state and props, we saw how data
is managed and how it flows between components. We also discussed other useful concepts, like
handling user input and fetching data from a remote API.
In this section, we’ll build another application step-by-step. We’ll dive even deeper into React’s
fundamentals. We’ll investigate a pattern that you can use to build React Native apps from scratch
and then put those steps to work to build a time-tracking application.
In this app, a user can add, delete, and modify various timers. Each timer corresponds to a different
task that the user would like to keep time for:
Time Tracking App
This app will have significantly more interactive capabilities than the one built in the last chapter.
As we’ll see, this will present us with some interesting challenges.
Getting started
This chapter assumes you’ve setup your system by following the steps at the beginning of the first
chapter.
As with all the chapters in this book, make sure you have the book’s sample code at the ready.
React Fundamentals 69
Previewing the app
Let’s begin by viewing the completed app. To try the completed app on your device:
On Android, you can scan this QR code using the Expo app:
QR Code
On iOS, you can navigate to the time-tracking/ directory within the sample code folder and
either preview it on the iOS simulator or send the link of the project URL to your device as we
explained in the previous chapter.
Play around with it to get a feel for all the functionality.
Breaking the app into components
Let’s start by breaking our app down into its components. As we noticed in our last project, visual
components usually map tightly to their respective React Native components. For example, we can
imagine that we’d want a Timer component for each timer:
React Fundamentals 70
Our application displays a list of timers and has a “+” icon at the top. We’re able to add new
timers to the list using this button. This “+” component is interesting because it has two distinct
representations. When the “+” button is pressed, the component changes into a form:
React Fundamentals 71
When the form is closed, the component changes back into a “+” button.
There are two approaches we could take. The first one is to have the parent component decide
whether or not to render a “+” component or a form component based on some piece of stateful
data. It could swap between the two children. However, this adds more responsibility to the parent
component. Since no other child components need this piece of information, it might make more
sense to have a new child component own the single responsibility of determining whether or not
to display a “+” button or a create timer form. We’ll call it ToggleableTimerForm. As a child, it can
either render the component TimerForm or the “+” button.
So, we’ve identified two components in addition to our root application component:
React Fundamentals 72
But the Timer component has a fair bit of functionality. As we saw in the completed version of the
app, each timer turns into a form when the user clicks “Edit”:
A single timer: Displaying time (left) vs. edit form (right)
In addition, timers delete themselves when “Remove” is pressed and have buttons for starting and
stopping. Do we need to break this up? And if so, how?
Displaying a timer and editing a timer are indeed two distinct UI components. They should be two
distinct React components. Like ToggleableTimerForm, we need some container component that
renders either the timer’s face or its edit form depending on if the timer is being edited.
React Fundamentals 73
We’ll call this EditableTimer. The child of EditableTimer will then be either a Timer component or
the edit form component. The form for creating and editing timers is very similar, so let’s assume
that we can use the component TimerForm in both contexts:
As for the other functionality of the timer, like the start and stop buttons, it’s a bit tough to determine
at this point whether or not they should be their own components. We can trust that the answers
will be more apparent after we’ve started writing some code and have a better idea of the general
structure of the components in our application.
So, we have our final component hierarchy, with some ambiguity around the final state of the timer
component:
React Fundamentals 74
App: Root container
EditableTimer: Displays either a timer or a timer’s edit form
* Timer: Displays a given timer
* TimerForm: Displays a given timer’s edit form
ToggleableTimerForm: Displays a form to create a new timer
* TimerForm: Displays a new timer’s create form
For all the buttons in the app, we’ll create and use a component called TimerButton.
7 step process
Now that we have a good understanding of the composition of our components, we’re ready to build
a static version of our app that only contains hardcoded data. As we noticed in the previous chapter,
many applications we build will require our top-level component to communicate with a server. In
these scenarios, the server will be the initial source of state, and React Native will render itself
according to the data the server provides. If our current app followed this pattern it would also
send updates to the server, like when a timer is started. However, for simplicity, in this chapter we’ll
render local state rather than communicating with a server.
React Fundamentals 75
It always simplifies things to start off with static components, as we did in the last chapter. The
static version of the app will not be interactive. Pressing buttons, for example, won’t do anything.
But this will enable us to lay the framework for the app, getting a clear idea of how the component
tree is organized.
Next, we can determine what the state should be for the app and in which component it should live.
At that point, we’ll have the data flow from parent to child in place. Then we can add inverse data
flow, propagating events from child to parent.
In fact, this follows from a handy process for developing a React Native app from scratch:
1. Break the app into components
2. Build a static version of the app
3. Determine what should be stateful
4. Determine in which component each piece of state should live
5. Hardcode initial states
6. Add inverse data flow
7. Add server communication (if present)
We followed this pattern in the last project:
1. Break the app into components
We looked at the desired UI and determined we wanted a custom SearchInput component.
2. Build a static version of the app
Our components started off without using state. Instead, we had our root App component pass down
location as a static prop to SearchInput.
3. Determine what should be stateful
In order for our application to become interactive, we had to be able to modify the search value of
the search input. The value submitted was our stateful location property.
4. Determine in which component each piece of state should live
Our root App component was responsible for managing the location, temperature, and weather
state parameters using React component class methods.
5. Hardcode initial state
We defined a hardcoded location value and passed it down to SearchInput as a custom prop.
6. Add inverse data flow
We defined the handleUpdateLocation function in our App container and passed it down in props
so that SearchInput could inform the parent of when our search input’s submit button is pressed.
7. Add server communication
React Fundamentals 76
We added server communication between our parent component and the MetaWeather API to
retrieve actual weather data.
These steps only serve as a guideline. You don’t necessarily have to follow it every time you build an
application, but you’ll likely internalize and become more accustomed to following this structure as
you build more applications. If steps in this process aren’t completely clear right now, don’t worry.
The purpose of this chapter is to familiarize yourself with this procedure.
We’ve already covered step (1) and have a good understanding of all of our components, except
for some uncertainty down at the Timer component. Step (2) is to build a static version of the app.
As in the last project, this amounts to defining React components, their hierarchy, and their HTML
representation. We avoid state for now.
Step 2: Build a static version of the app
Prepare the app
Before beginning, run the following commands in your terminal to create a new React Native app:
create-react-native-app time-tracking --scripts-version 1.14.0
cd time-tracking
yarn start
App
Let’s start off by writing our App component in the file App.js. We’ll begin with our imports:
time-tracking/1/App.js
import React from 'react';
import { StyleSheet, View, ScrollView, Text } from 'react-native';
import EditableTimer from './components/EditableTimer';
import ToggleableTimerForm from './components/ToggleableTimerForm';
After importing the core React Native components we’ll be using in App, we import EditableTimer
and ToggleableTimerForm. We’ll be implementing those shortly.
We’ll have our App component render both ToggleableTimerForm and a couple of EditableTimer
components. Because we’re building the static version of our app, we’ll manually set all the props:
React Fundamentals 77
time-tracking/1/App.js
export default class App extends React.Component {
render() {
return (
<View style={styles.appContainer}>
<View style={styles.titleContainer}>
<Text style={styles.title}>Timers</Text>
</View>
<ScrollView style={styles.timerList}>
<ToggleableTimerForm isOpen={false} />
<EditableTimer
id="1"
title="Mow the lawn"
project="House Chores"
elapsed="8986300"
isRunning
/>
<EditableTimer
id="2"
title="Bake squash"
project="Kitchen Chores"
elapsed="3890985"
editFormOpen
/>
</ScrollView>
</View>
);
}
}
At the top, we display a title (“Timers”) inside of a Text component. We’ll look at the styles object
in a moment.
After our title, we render the rest of the components in a ScrollView component. The built-in
ScrollView component in React Native is responsible for wrapping components within a scrolling
container.
We’re passing down one prop to ToggleableTimerForm: isOpen. This is used by the child component
to determine whether to render a “+” or TimerForm. When ToggleableTimerForm is “open” the form
is being displayed.
We also include two separate EditableTimer components within App. We’ll dig into each of these
props when we build the component. Notably, isRunning specifies whether the timer is running and
editFormOpen specifies whether EditableTimer should display the timer’s face or its edit form.
React Fundamentals 78
Note that we don’t explicitly set any values for the props isRunning on the first EditableTimer or
editFormOpen on the second:
time-tracking/1/App.js
<EditableTimer
id="1"
title="Mow the lawn"
project="House Chores"
elapsed="8986300"
isRunning
/>
<EditableTimer
id="2"
title="Bake squash"
project="Kitchen Chores"
elapsed="3890985"
editFormOpen
/>
This is a style for boolean props you’ll often encounter in React Native apps. When no explicit value
is passed, the prop defaults to true. So <ToggleableTimerForm isOpen /> will give the same result
as <ToggleableTimerForm isOpen={true}/>. Conversely, when a prop is absent it is undefined. This
means that for the first timer editFormOpen is “falsy.
ScrollView renders all of its components at once, even those not currently shown in the
screen.
Last, here are the styles we’re using:
time-tracking/1/App.js
const styles = StyleSheet.create({
appContainer: {
flex: 1,
},
titleContainer: {
paddingTop: 35,
paddingBottom: 15,
borderBottomWidth: 1,
borderBottomColor: '#D6D7DA',
},
title: {
fontSize: 18,
React Fundamentals 79
fontWeight: 'bold',
textAlign: 'center',
},
timerList: {
paddingBottom: 15,
},
});
We’re not going to focus on styles in this chapter so feel free to just copy over the styles object for
each component.
EditableTimer
With all of our child components, we’ll save their respective files within a components subdirectory.
Let’s create components/EditableTimer.js.
First, we’ll begin by implementing TimerForm and Timer. We’ll be creating those shortly:
time-tracking/1/components/EditableTimer.js
import React from 'react';
import TimerForm from './TimerForm';
import Timer from './Timer';
EditableTimer will either return a timer’s face (Timer) or a timer’s edit form (TimerForm) based on
the prop editFormOpen. We don’t anticipate this component will ever manage state.
So far, we’ve written React components as ES6 classes that extend React.Component. However,
there’s another way to declare React components: as functions.
Let’s see what that looks like:
time-tracking/1/components/EditableTimer.js
export default function EditableTimer({
id,
title,
project,
elapsed,
isRunning,
editFormOpen,
}) {
if (editFormOpen) {
return <TimerForm id={id} title={title} project={project} />;
React Fundamentals 80
}
return (
<Timer
id={id}
title={title}
project={project}
elapsed={elapsed}
isRunning={isRunning}
/>
);
}
EditableTimer is a regular JavaScript function. In React, we call components written this way state-
less functional components or functional components for short. While we can write EditableTimer
using either component style, it’s a perfect candidate to be written as a function.
Think of functional components as components that only need to implement the render() method.
They don’t manage state and don’t need any of React’s special lifecycle hooks.
Throughout this book, we’ll refer to the two different types as class components and
functional components.
Note that the props are passed in as the first argument to the function. We don’t use this when
working with functional components. Here, we use destructuring to extract all the props from the
props object.
The component’s render method switches on the prop editFormOpen. If true, we render a TimerForm.
Otherwise, we render Timer.
As we saw in App, this component receives six props. This component passes down the props id,
title and project to TimerForm. For Timer, we pass down all the timer attributes.
Benefits of functional components
Why would we want to use functional components? There are two main reasons:
First, using functional components where possible encourages developers to manage state in fewer
locations. This makes our programs easier to reason about.
Second, using functional components are a great way to create reusable components. Because
functional components need to have all their configuration passed from the outside, they are easy
to reuse across apps or projects.
A good rule of thumb is to use functional components as much as possible. If we don’t need any
lifecycle methods and can get away with only a render() function, using a functional component
is a great choice.
React Fundamentals 81
Note that React still allows us to set propTypes and defaultProps on functional components.
TimerForm
TimerForm will contain two TextInput fields for editing a timer’s title and project. We’ll also add
a pair of buttons at the bottom.
Like EditableTimer, we can write this component as a functional component:
time-tracking/1/components/TimerForm.js
import React from 'react';
import { StyleSheet, View, Text, TextInput } from 'react-native';
import TimerButton from './TimerButton';
export default function TimerForm({ id, title, project }) {
const submitText = id ? 'Update' : 'Create';
return (
<View style={styles.formContainer}>
<View style={styles.attributeContainer}>
<Text style={styles.textInputTitle}>Title</Text>
<View style={styles.textInputContainer}>
<TextInput
style={styles.textInput}
underlineColorAndroid="transparent"
defaultValue={title}
/>
</View>
</View>
<View style={styles.attributeContainer}>
<Text style={styles.textInputTitle}>Project</Text>
<View style={styles.textInputContainer}>
<TextInput
style={styles.textInput}
underlineColorAndroid="transparent"
defaultValue={project}
/>
</View>
</View>
<View style={styles.buttonGroup}>
React Fundamentals 82
<TimerButton small color="#21BA45" title={submitText} />
<TimerButton small color="#DB2828" title="Cancel" />
</View>
</View>
);
}
const styles = StyleSheet.create({
formContainer: {
backgroundColor: 'white',
borderColor: '#D6D7DA',
borderWidth: 2,
borderRadius: 10,
padding: 15,
margin: 15,
marginBottom: 0,
},
attributeContainer: {
marginVertical: 8,
},
textInputContainer: {
borderColor: '#D6D7DA',
borderRadius: 2,
borderWidth: 1,
marginBottom: 5,
},
textInput: {
height: 30,
padding: 5,
fontSize: 12,
},
textInputTitle: {
fontSize: 14,
fontWeight: 'bold',
marginBottom: 5,
},
buttonGroup: {
flexDirection: 'row',
justifyContent: 'space-between',
},
});
React Fundamentals 83
We wrap each of our form elements in a View container. Each input field has a label (“Title” and
“Project”) above a TextInput.
At the end of the component, we have a button group with two TimerButton instances. We’ll create
this component in a bit.
Let’s take a closer look at how we’ve set up TextInput for the timer’s title:
time-tracking/1/components/TimerForm.js
<TextInput
style={styles.textInput}
underlineColorAndroid="transparent"
defaultValue={title}
/>
And for the timer’s project:
time-tracking/1/components/TimerForm.js
<TextInput
style={styles.textInput}
underlineColorAndroid="transparent"
defaultValue={project}
/>
Aside from adding styles using the style prop, we’re also using the TextInput component’s
defaultValue property. When the form is used for editing as it is here, we want the fields to be
populated with the current title and project values for this timer. Using defaultValue initializes
these fields with the current values, as desired.
Later, we’ll use TimerForm again within ToggleableTimerForm for creating timers.
ToggleableTimerForm will not pass TimerForm the title or project props. We’ll use
defaultProps to default these values to empty strings.
At the beginning of the component function, before the return statement, we define the variable
submitText. This variable uses the presence of props.id to determine what text the submit button
at the bottom of the form should display. If id is present, we know we’re editing an existing timer,
so it displays “Update. Otherwise, it displays “Create.
With all of this logic in place, TimerForm is prepared to render a form for creating a new timer or
editing an existing one.
React Fundamentals 84
We used an expression with the ternary operator to set the value of submitText. The syntax
is:
condition ? expression1 : expression2
If the condition is true, the ternary expression evaluates to expression1. Otherwise, it
evaluates to expression2. In our example, the variable submitText is set to the result of
the ternary expression.
TimerButton
Now let’s set up a component that we can use for all the buttons in our application, TimerButton.
Again, we can write this as a functional component:
time-tracking/1/components/TimerButton.js
1 import { StyleSheet, Text, TouchableOpacity } from 'react-native';
2 import React from 'react';
3
4 export default function TimerButton({ color, title, small, onPress }) {
5 return (
6 <TouchableOpacity
7 style={[styles.button, { borderColor: color }]}
8 onPress={onPress}
9 >
10 <Text
11 style={[
12 styles.buttonText,
13 small ? styles.small : styles.large,
14 { color },
15 ]}
16 >
17 {title}
18 </Text>
19 </TouchableOpacity>
20 );
21 }
22
23 const styles = StyleSheet.create({
24 button: {
25 marginTop: 10,
26 minWidth: 100,
27 borderWidth: 2,
React Fundamentals 85
28 borderRadius: 3,
29 },
30 small: {
31 fontSize: 14,
32 padding: 5,
33 },
34 large: {
35 fontSize: 16,
36 padding: 10,
37 },
38 buttonText: {
39 textAlign: 'center',
40 fontWeight: 'bold',
41 },
42 title: {
43 fontSize: 14,
44 fontWeight: 'bold',
45 },
46 elapsedTime: {
47 fontSize: 18,
48 fontWeight: 'bold',
49 textAlign: 'center',
50 paddingVertical: 10,
51 },
52 });
React Native provides a built-in Button component, but it only allows for limited customization. For
this reason, we’re leveraging TouchableOpacity, which renders a wrapper to allow for components
to respond with opacity changes when pressed.
For easier customization, we’ve included color, title, and small as props that will allow us to
change how our button looks. The title prop is responsible for the button text while the color prop
changes the text and border colors. The small prop is a boolean prop passed in to render a smaller
button with slightly different styling.
Since we plan on using this component in multiple places in our app, we’ve defined an onPress prop
in order to fire a specific function that’s passed into our component when the button is pressed. We’re
not using it currently in TimerForm but we will as soon as we add actual data in our application.
TouchableOpacity accepts an activeOpacity prop that allows us to determine what the
opacity of the view should be when pressed. This defaults to a value of 0.2.
React Fundamentals 86
ToggleableTimerForm
Let’s turn our attention next to ToggleableTimerForm. Recall that this is a wrapper component
around TimerForm. It will display either a “+” or a TimerForm. Right now, it accepts a single prop,
isOpen, from its parent that instructs its behavior:
time-tracking/1/components/ToggleableTimerForm.js
import React from 'react';
import { StyleSheet, View } from 'react-native';
import TimerButton from './TimerButton';
import TimerForm from './TimerForm';
export default function ToggleableTimerForm({ isOpen }) {
return (
<View style={[styles.container, !isOpen && styles.buttonPadding]}>
{isOpen ? <TimerForm /> : <TimerButton title="+" color="black" />}
</View>
);
}
const styles = StyleSheet.create({
container: {
paddingVertical: 10,
},
buttonPadding: {
paddingHorizontal: 15,
},
});
As noted earlier, TimerForm does not receive any props from ToggleableTimerForm. As such, its
title and project fields will be rendered empty.
We’re using a ternary operator again here to either return TimerForm or render a “+” button. You
could make a case that this should be its own component (say PlusButton) but at present we’ll keep
the code inside ToggleableTimerForm.
Timer
Time for the Timer component.
As with all projects in this book, the sample code for this project comes with a utils/ directory
that contains various functions that will aid in the construction of this app. We’ll be using one of
React Fundamentals 87
those functions now. If you haven’t already, go ahead and copy over time-tracking/utils/ from
the sample code to your project directory now.
With utils/ in place, let’s take a look at our first version of Timer:
time-tracking/1/components/Timer.js
import React from 'react';
import { StyleSheet, View, Text } from 'react-native';
import { millisecondsToHuman } from '../utils/TimerUtils';
import TimerButton from './TimerButton';
export default function Timer({ title, project, elapsed }) {
const elapsedString = millisecondsToHuman(elapsed);
return (
<View style={styles.timerContainer}>
<Text style={styles.title}>{title}</Text>
<Text>{project}</Text>
<Text style={styles.elapsedTime}>{elapsedString}</Text>
<View style={styles.buttonGroup}>
<TimerButton color="blue" small title="Edit" />
<TimerButton color="blue" small title="Remove" />
</View>
<TimerButton color="#21BA45" title="Start" />
</View>
);
}
const styles = StyleSheet.create({
timerContainer: {
backgroundColor: 'white',
borderColor: '#d6d7da',
borderWidth: 2,
borderRadius: 10,
padding: 15,
margin: 15,
marginBottom: 0,
},
title: {
fontSize: 14,
fontWeight: 'bold',
},
elapsedTime: {
React Fundamentals 88
fontSize: 26,
fontWeight: 'bold',
textAlign: 'center',
paddingVertical: 15,
},
buttonGroup: {
flexDirection: 'row',
justifyContent: 'space-between',
},
});
The elapsed prop in this app is in milliseconds. This is the representation of the data that React will
keep. This is a good representation for machines, but we want to show our users a more human-
readable format.
We use a function defined in ./utils/TimerUtils, millisecondsToHuman(). You can pop open that
file if you’re curious about how it’s implemented. The string it renders is in the format ‘HH:MM:SS’.
Note that we could store elapsed in seconds as opposed to milliseconds, but JavaScript’s
time functionality is all in milliseconds. We keep elapsed consistent with this for simplicity.
Try it out
With all of our components laid out, let’s boot up the React Native packager to see our app so far:
React Fundamentals 89
Tweak some of the props and refresh to see the results. For example:
Flip the prop passed down to ToggleableTimerForm from false to true and see the timer form
render in the place of the “+” button:
React Fundamentals 90
Remove the editFormOpen prop in the second EditableTimer component within App and
witness the component flip the child it renders accordingly:
React Fundamentals 91
To review, our App component currently renders a ToggleableTimerForm component and two
EditableTimer components.
ToggleableTimerForm renders either a “+” or a TimerForm based on the prop isOpen.
EditableTimer renders either Timer or TimerForm based on the prop editFormOpen. Timer and
TimerForm are our app’s bottom-level components. They hold the majority of the screen’s UI. The
components above them are primarily concerned with orchestration.
So far, we’ve used hardcoded props to pass data around our app. But in order to enhance our app
with interactivity, we must evolve it from its static existence to a mutable one. As we saw in the last
chapter, in React we use state to accomplish this.
Step 3: Determine what should be stateful
Before introducing state, we need to determine what, exactly, should be stateful. Let’s start by
collecting all of the data that’s consumed by each component in our static app. In our static app,
data will be wherever we are defining or using props. We will then determine which of that data
should be stateful.
App
React Fundamentals 92
This declares two child components. It sets one prop, which is the isOpen boolean that is passed
down to ToggleableTimerForm.
EditableTimer
This uses the prop editFormOpen and also accepts all the attributes of a timer.
Timer
This uses all the attributes for a timer.
TimerForm
This has two interactive input fields, one for title and one for project. When editing an existing
timer, these fields are initialized with the timer’s current values.
State criteria
We can apply criteria to determine if data should be stateful:
These questions are from the excellent article by Facebook called “Thinking In React. You
can read the original article here
50
.
1. Is it passed in from a parent via props? If so, it probably isn’t state.
A lot of the data used in our child components are already listed in their parents. This criterion helps
us de-duplicate.
For example, “timer properties” is listed multiple times. When we see the properties declared in
EditableTimer, we can consider it state. But when we see it elsewhere, it’s not.
2. Does it change over time? If not, it probably isn’t state.
This is a key criterion of stateful data: it changes.
3. Can you compute it based on any other state or props in your component? If so, it’s not
state.
For simplicity, we want to strive to represent state with as few data points as possible.
Applying the criteria
App
isOpen boolean for ToggleableTimerForm and timer properties for EditableTimer
50
https://facebook.github.io/react/docs/thinking-in-react.html
React Fundamentals 93
Stateful. The data is defined here. It changes over time. And it cannot be computed from other state
or props.
timer attributes
Stateful. We define the data on each EditableTimer here. This data is mutable. And it cannot be
computed from other state or props.
EditableTimer
editFormOpen for a given timer
Stateful. The data is defined here. It changes over time. And it cannot be computed from other state
or props.
Timer
Timer properties
In this context, not stateful. Properties are passed down from the parent.
TimerForm
We might be tempted to conclude that TimerForm doesn’t manage any stateful data, as title and
project are props passed down from the parent. However, as saw with our SearchInput component
in the previous chapter, components that use TextInput can be special state managers in their own
right these components often maintain the value of the input field as state.
So, outside of TimerForm, we’ve identified our stateful data:
The list of timers and properties of each timer
Whether or not the edit form of a timer is open
Whether or not the create form is open
Step 4: Determine in which component each piece of
state should live
While the data we’ve determined to be stateful might live in certain components in our static app,
this does not indicate the best position for it in our stateful app. Our next task is to determine the
optimal place for each of our three discrete pieces of state to live.
This can be challenging at times but, again, we can apply the following steps from Facebook’s guide
Thinking in React
51
to help us with the process:
51
https://facebook.github.io/react/docs/thinking-in-react.html
React Fundamentals 94
For each piece of state:
Identify every component that renders something based on that state.
Find a common owner component (a single component above all the components
that need the state in the hierarchy).
Either the common owner or another component higher up in the hierarchy should
own the state.
If you can’t find a component where it makes sense to own the state, create a new
component simply for holding the state and add it somewhere in the hierarchy above
the common owner component.
Let’s apply this method to our application:
The list of timers and attributes of each timer
At first glance, we may be tempted to conclude that App does not appear to use this state. Instead, the
first component that uses this state is EditableTimer. We might think it would be wise to move timer
attributes into the EditableTimer component’s state as opposed to passing them down as props.
While this may be the case for displaying timers, modifying them, and deleting them, what about
creating them? ToggleableTimerForm does not need the state to render, but it can affect state. It
needs to be able to insert a new timer. It will propagate the data for the new timer up to the root
App.
Therefore, App is truly the common owner. It renders EditableTimer components by passing down
timer state. It can handle modifications from EditableTimer and creates from ToggleableTimerForm,
mutating the state. The new state will then flow downward through EditableTimer via props.
Whether or not the edit form of a timer is open
In our static app, App specifies whether or not an EditableTimer should be rendered with its edit
form open. Technically, though, this state could just live in each individual EditableTimer. No parent
component in the hierarchy depends on this data.
Storing the state in EditableTimer will be fine for our current needs. But there are a few requirements
that might require us to “hoist” this state up higher in the component hierarchy in the future.
For instance, what if we wanted to impose a restriction such that only one form, including the create
form, could be open at a time? Then it would make sense for App to own the state, as it would need
to inspect it to determine whether to allow for another form to open.
Visibility of the create form
App doesn’t appear to care about whether ToggleableTimerForm is open or closed. It feels safe to
reason that the state can just live inside ToggleableTimerForm itself.
So, in summary, we’ll have three pieces of state each in three different components:
React Fundamentals 95
Timer data will be owned and managed by App.
Each EditableTimer will manage the state of its timer edit form.
The ToggleableTimerForm will manage the state of its form visibility.
Step 5: Hardcode initial states
We’re now well prepared to make our app stateful. We’ll define our initial states within the
components themselves. This means hardcoding a list of timers in the top-level component, App.
For our two other pieces of state, we’ll have the components’ forms closed by default.
After we’ve added initial state to a parent component, we’ll make sure our props are properly
established in its children.
Adding state to App
Let’s start by modifying App to hold the timer data in state.
We’ll be using the npm library uuid
52
to generate ids for each of our timers. The library’s function
uuidv4() will randomly generate a Universally Unique IDentifier
53
for each of our timers.
A UUID is a string that looks like this:
2030efbd-a32f-4fcc-8637-7c410896b3e3
First, in your console, install the library:
yarn add uuid
Then, at the top of App.js, import the uuidv4() function:
time-tracking/2/App.js
import uuidv4 from 'uuid/v4';
Next, we’ll initialize the component’s state to an array of two timer objects. This will give us a list
of timers to play with when we open the app:
52
https://www.npmjs.com/package/uuid
53
https://en.wikipedia.org/wiki/Universally_unique_identifier
React Fundamentals 96
time-tracking/2/App.js
export default class App extends React.Component {
state = {
timers: [
{
title: 'Mow the lawn',
project: 'House Chores',
id: uuidv4(),
elapsed: 5456099,
isRunning: true,
},
{
title: 'Bake squash',
project: 'Kitchen Chores',
id: uuidv4(),
elapsed: 1273998,
isRunning: false,
},
],
};
We set the initial state to an object with the key timers. timers points to an array with two hardcoded
timer objects.
As in the previous chapter, we’re leaning on the Babel plugin transform-class-properties
to help simplify how we define our initial state.
Below, in render, we’ll use state.timers to generate an array of EditableTimer components. Each
will be derived from an individual object in the timers array that’s being passed in as a prop. We’ll
use map to do so:
time-tracking/2/App.js
render() {
const { timers } = this.state;
return (
<View style={styles.appContainer}>
<View style={styles.titleContainer}>
<Text style={styles.title}>Timers</Text>
</View>
<ScrollView style={styles.timerList}>
React Fundamentals 97
<ToggleableTimerForm />
{timers.map(({ title, project, id, elapsed, isRunning }) => (
<EditableTimer
key={id}
id={id}
title={title}
project={project}
elapsed={elapsed}
isRunning={isRunning}
/>
))}
</ScrollView>
</View>
);
}
The rendered UI of the component ends up being an array of EditableTimer components:
[
<EditableTimer
timer={{
title: 'Mow the lawn',
project: 'House Chores',
id: // random UUID,
elapsed: 5456099,
isRunning: true,
}}
/>,
<EditableTimer
timer={{
title: 'Bake squash',
project: 'Kitchen Chores',
id: // random UUID,
elapsed: 1273998,
isRunning: false,
}}
/>
]
Notably, we’re able to represent the EditableTimer component instance in JSX inside of return. It
might seem odd at first that we’re able to have a JavaScript array of JSX elements, but remember
that Babel will transpile the JSX representation of each EditableTimer (<EditableTimer />) into
regular JavaScript.
React Fundamentals 98
If you’re interested in how this compiles, please refer to the Appendix.
Note the use of the key={timer.id} prop. The key prop is not used by our EditableTimer
component but by the React Native framework. It’s a special property that we discuss deeper
in the next chapter “Core Components. For the time being, it’s enough to note that this
property needs to be unique per React Native component in a list.
Array’s map()
If you’re unfamiliar with the map method, it takes a function as an argument and calls it with
each item inside of the array and builds a new array by using the return value from each
function call.
Since the timers array has two items, map will call this function twice, once for each timer.
When map calls this function, it passes in as the first argument an item. The return value
from this function call is inserted into the new array that map is constructing. After handling
the last item, map returns this new array. Here, we’re rendering this new array within our
render() method.
Props vs. state
Let’s take a step back and reflect on the difference between props and state again. What existed as
mutable state in App is passed down as immutable props to EditableTimer.
We talked at length about what qualifies as state and where state should live. Mercifully, we do not
need to have an equally lengthy discussion about props. Once you understand state, you can see
how props act as its one-way data pipeline. State is managed in some select parent components
and then that data flows down through children as props.
If state is updated, the component managing that state re-renders by calling render(). This, in turn,
causes any of its children to re-render as well. And the children of those children. And on and on
down the chain.
Let’s continue our own march down the chain.
Adding state to EditableTimer
In the static version of our app, EditableTimer relied on editFormOpen as a prop to be passed down
from the parent. We decided that this state could actually live here in the component itself.
Because this component will actually manage state, we’ll need to change it from a functional
component to a class component.
We’ll set the initial value of editFormOpen to false, which means that the form starts off as closed:
React Fundamentals 99
time-tracking/2/components/EditableTimer.js
export default class EditableTimer extends React.Component {
state = {
editFormOpen: false,
};
render() {
const { id, title, project, elapsed, isRunning } = this.props;
const { editFormOpen } = this.state;
if (editFormOpen) {
return <TimerForm id={id} title={title} project={project} />;
}
return (
<Timer
id={id}
title={title}
project={project}
elapsed={elapsed}
isRunning={isRunning}
/>
);
}
}
Timer remains stateless
If you look at Timer, you’ll see that it does not need to be modified to include state. It has been using
exclusively props and is so far unaffected by our current refactor.
Adding state to ToggleableTimerForm
We know that we’ll need to tweak ToggleableTimerForm as we’ve assigned it some stateful
responsibility. We want to have the component manage the state isOpen.
We’ll initialize the state to a “closed” state at the top of the component:
React Fundamentals 100
time-tracking/2/components/ToggleableTimerForm.js
export default class ToggleableTimerForm extends React.Component {
state = {
isOpen: false,
};
Next, we’ll define a function that toggles the state of the form to open:
time-tracking/2/components/ToggleableTimerForm.js
handleFormOpen = () => {
this.setState({ isOpen: true });
};
Finally, we’ll modify the component’s render() method to include our app’s first piece of interactiv-
ity. We’ll switch off of isOpen to determine whether we should render the “+” button or a TimerForm.
We’ll set handleFormOpen as the onPress handler for TimerButton:
time-tracking/2/components/ToggleableTimerForm.js
render() {
const { isOpen } = this.state;
return (
<View style={[styles.container, !isOpen && styles.buttonPadding]}>
{isOpen ? (
<TimerForm />
) : (
<TimerButton title="+" color="black" onPress={this.handleFormOpen} />
)}
</View>
);
}
If you remember, we created TimerButton to accept an onPress prop which is passed down to
the onPress action of the TouchableOpacity within. TouchableOpacity is a built-in React Native
component. When it is pressed, it will invoke its onPress handler. TimerButton passes along its own
onPress prop directly to TouchableOpacity.
Therefore, when the TimerButton is pressed, the function handleFormOpen() will be invoked.
handleFormOpen() modifies the state, setting isOpen to true. This causes the ToggleableTimerForm
component to re-render. When render() is called this second time around, this.state.isOpen is
true and ToggleableTimerForm renders TimerForm. Neat.
React Fundamentals 101
As we explored in the last chapter, we are writing the handleFormOpen() function as a
property initializer (i.e. using an arrow function) in order to ensure this inside the function
is bound to the component. React will automatically bind class methods corresponding to
the component API (like render and componentDidMount) to the component for us.
Our updated ToggleableTimerForm, in full:
time-tracking/2/components/ToggleableTimerForm.js
export default class ToggleableTimerForm extends React.Component {
state = {
isOpen: false,
};
handleFormOpen = () => {
this.setState({ isOpen: true });
};
render() {
const { isOpen } = this.state;
return (
<View style={[styles.container, !isOpen && styles.buttonPadding]}>
{isOpen ? (
<TimerForm />
) : (
<TimerButton title="+" color="black" onPress={this.handleFormOpen} />
)}
</View>
);
}
}
Adding state to TimerForm
We mentioned earlier that TimerForm would manage state as it includes a form. In React Native,
forms are stateful.
Recall that TimerForm includes two input fields:
React Fundamentals 102
These input fields are modifiable by the user. In React Native, all modifications that are made to a
component should be handled and kept in state. This includes changes like the modification of an
input field. The best way to understand this is to see what it looks like.
To make these input fields stateful, we can make our component stateful and initialize state at the
top of our component:
time-tracking/2/components/TimerForm.js
export default class TimerForm extends React.Component {
constructor(props) {
super(props);
const { id, title, project } = props;
this.state = {
title: id ? title : '',
project: id ? project : '',
};
}
Our state object has two properties, each corresponding to an input field that TimerForm manages.
If TimerForm is creating a new timer as opposed to editing an existing one, the id prop will be
undefined. In that case, we initialize both properties to a blank string ('') using ternary operators.
Otherwise, when this form is editing a timer, we’ll want to set both values to their respective prop
values.
Note that because we’re checking and defining our state based on props, we’re using the constructor()
for state initialization instead of defining state as a class property.
We want to avoid initializing title or project to undefined. That’s because the value of
an input field can’t technically ever be undefined. If it’s empty, its value in JavaScript is a
blank string.
In our first pass at building this component, we used defaultValue to set the initial state of the
TextInput fields based on props. But the defaultValue prop only sets the value of the TextInput
React Fundamentals 103
for the initial render. Instead of using defaultValue, we can connect our input fields directly to our
component’s state using value. We could do something like this:
<TextInput value={this.state.title} />
With this, our input fields would be driven by state. Whenever either of our state properties change,
our input fields would be updated to reflect the new value.
However, this misses a key ingredient: We don’t currently have any way for the user to modify
this state. The input field will start off in-sync with the component’s state. But the moment the user
makes a modification, the input field will become out-of-sync with the component’s state.
We can fix this by using React Native’s onChangeText prop for TextInput components. Like onPress
for a button component, we can set onChangeText to a function like we did in our previous chapter.
Whenever the input field is changed, React will invoke the function specified.
Let’s set the onChangeText attributes on both input fields to functions we’ll define next. For our title
input field:
time-tracking/2/components/TimerForm.js
<TextInput
style={styles.textInput}
underlineColorAndroid="transparent"
onChangeText={this.handleTitleChange}
value={title}
/>
And similarly for project:
time-tracking/2/components/TimerForm.js
<TextInput
style={styles.textInput}
underlineColorAndroid="transparent"
onChangeText={this.handleProjectChange}
value={project}
/>
The functions handleTitleChange and handleProjectChange will both modify their respective
properties in state. Here’s what they look like:
React Fundamentals 104
time-tracking/2/components/TimerForm.js
handleTitleChange = title => {
this.setState({ title });
};
handleProjectChange = project => {
this.setState({ project });
};
When React Native invokes the function passed to onChangeText, it invokes the function with the
changed text passed as the argument. With this, we update the state to the new value of the input
field.
Using a combination of state, the value attribute, and the onChangeText attribute is the
canonical method we use to write form elements in React Native.
Our updated TimerForm component, in full:
time-tracking/2/components/TimerForm.js
export default class TimerForm extends React.Component {
constructor(props) {
super(props);
const { id, title, project } = props;
this.state = {
title: id ? title : '',
project: id ? project : '',
};
}
handleTitleChange = title => {
this.setState({ title });
};
handleProjectChange = project => {
this.setState({ project });
};
render() {
const { id } = this.props;
const { title, project } = this.state;
React Fundamentals 105
const submitText = id ? 'Update' : 'Create';
return (
<View style={styles.formContainer}>
<View style={styles.attributeContainer}>
<Text style={styles.textInputTitle}>Title</Text>
<View style={styles.textInputContainer}>
<TextInput
style={styles.textInput}
underlineColorAndroid="transparent"
onChangeText={this.handleTitleChange}
value={title}
/>
</View>
</View>
<View style={styles.attributeContainer}>
<Text style={styles.textInputTitle}>Project</Text>
<View style={styles.textInputContainer}>
<TextInput
style={styles.textInput}
underlineColorAndroid="transparent"
onChangeText={this.handleProjectChange}
value={project}
/>
</View>
</View>
<View style={styles.buttonGroup}>
<TimerButton small color="#21BA45" title={submitText} />
<TimerButton small color="#DB2828" title="Cancel" />
</View>
</View>
);
}
}
To recap, here’s an example of the lifecycle of TimerForm:
1. On the page is a timer with the title “Mow the lawn.
2. The user toggles open the edit form for this timer, mounting TimerForm to the screen.
3. TimerForm initializes the state property title to the string "Mow the lawn".
4. The user modifies the title input field, changing it to the value "Cut the grass".
5. With every keystroke, React invokes handleTitleChange. The internal state of title is kept
in-sync with what the user sees on the page.
React Fundamentals 106
With TimerForm refactored, we’ve finished establishing our stateful data inside our components.
And we’ve assembled our downward data pipeline, props.
We’re ready and perhaps a bit eager to build out interactivity using inverse data flow. But
before we do, let’s save and reload the app to ensure everything is working.
Try it out
We expect to see new example timers based on the hardcoded data in App. We also expect pressing
the “+” button toggles open a form:
Step 6: Add inverse data flow
As we saw in the last chapter, children communicate with parents by calling functions that are
provided to them via props. In our weather app, when our search field was submitted with a value,
SearchInput didn’t do any data management. Instead, it called a function given to it by App, which
was then able to manage state accordingly.
We are going to need inverse data flow in two areas:
TimerForm needs to propagate create and update events (create while under ToggleableTimerForm
and update while under EditableTimer). Both events will eventually reach our top level App.
Timer has a fair amount of behavior. It needs to handle delete and edit press, as well as the
start and stop timer logic.
Let’s start with TimerForm.
React Fundamentals 107
TimerForm
To get a clear idea of what exactly TimerForm will require, we’ll start by adding event handlers to it
and then we’ll work our way backwards up the hierarchy.
TimerForm needs two event handlers:
When the form is submitted (creating or updating a timer)
When the “Cancel” button is pressed (closing the form)
TimerForm
will receive two functions as props to handle each event. The parent component that uses
TimerForm is responsible for providing these functions:
props.onFormSubmit(): called when the form is submitted
props.onFormClose()
: called when the “Cancel” button is pressed
As we’ll see soon, this enables the parent component to determine what the behavior should be
when these events occur.
Let’s first add onFormClose to the props being destructured in the component’s render method:
time-tracking/3/components/TimerForm.js
render() {
const { id, onFormClose } = this.props;
We’ll then modify the buttons on TimerForm by specifying onPress props for each:
time-tracking/3/components/TimerForm.js
<View style={styles.buttonGroup}>
<TimerButton
small
color="#21BA45"
title={submitText}
onPress={this.handleSubmit}
/>
<TimerButton
small
color="#DB2828"
title="Cancel"
onPress={onFormClose}
/>
</View>
React Fundamentals 108
The onPress prop for the “Submit” button specifies the function this.handleSubmit, which we’ll
define next. The onPress prop for the “Cancel” button specifies the prop onFormClose directly.
Now that we’ve seen how we’ll use handleSubmit, let’s write it. Declare this function above
render():
time-tracking/3/components/TimerForm.js
handleSubmit = () => {
const { onFormSubmit, id } = this.props;
const { title, project } = this.state;
onFormSubmit({
id,
title,
project,
});
};
Again, we’re working bottom-up right now. So the handleSubmit() method calls the anticipated
function onFormSubmit() which we’ll write in a moment. It passes in a data object with id, title,
and project attributes.
Notice that we’re reading id via props and reading title and project from state. This is because we
want to supply the function with the up-to-date values of title and project (in state) as opposed
to the initial values (supplied as props).
ToggleableTimerForm
Let’s follow the submit event from TimerForm as it bubbles up the component hierarchy. First, we’ll
modify ToggleableTimerForm. We need it to define and pass down two prop-functions to TimerForm:
onFormClose() and onFormSubmit().
Let’s update the component’s render() method first:
time-tracking/4/components/ToggleableTimerForm.js
render() {
const { isOpen } = this.state;
return (
<View style={[styles.container, !isOpen && styles.buttonPadding]}>
{isOpen ? (
<TimerForm
onFormSubmit={this.handleFormSubmit}
onFormClose={this.handleFormClose}
React Fundamentals 109
/>
) : (
<TimerButton title="+" color="black" onPress={this.handleFormOpen} />
)}
</View>
);
}
We pass in two functions as props to TimerForm. As we’ve seen, functions are just like any other
prop.
Let’s write handleFormClose() first:
time-tracking/4/components/ToggleableTimerForm.js
handleFormClose = () => {
this.setState({ isOpen: false });
};
Now, what might handleFormSubmit() look like? ToggleableTimerForm is not the manager of timer
state. So ToggleableTimerForm should expect a new prop, onFormSubmit() from App. ToggleableTimerForm
should, in turn, pass this down to TimerForm. When the user submits a form down in TimerForm,
they’ll be invoking a function defined up in App that modifies the timer state. ToggleableTimerForm
is just a proxy of this function.
So, we might be tempted to just pass this anticipated prop-function directly to TimerForm like this:
<TimerForm
onFormSubmit={this.props.onFormSubmit}
onFormClose={this.handleFormClose}
/>
However, consider this: after the user clicks “Create to create a timer, we actually want to close
ToggleableTimerForm. We’ll want to intercept this event so that we can set isOpen to false.
To do this, here’s what handleFormSubmit() looks like:
React Fundamentals 110
time-tracking/4/components/ToggleableTimerForm.js
handleFormSubmit = timer => {
const { onFormSubmit } = this.props;
onFormSubmit(timer);
this.setState({ isOpen: false });
};
The handleFormSubmit() method accepts the argument timer and passes it along to onFormSubmit().
Recall that in TimerForm this argument is an object containing the desired timer properties. After
invoking onFormSubmit(), handleFormSubmit() calls setState() to close its form.
Although we’re not adding server communication in this chapter, let’s try and visualize how
submitting the form would work if we did.
The result of onFormSubmit() will not impact whether or not the form is closed. We invoke
onFormSubmit(), which may eventually create an asynchronous call to a server. Execution
will continue before we hear back from the server which means setState() will be called.
If onFormSubmit() fails such as if the server is temporarily unreachable we’d ideally
have some way to display an error message and re-open the form.
App
We’ve reached the top of the hierarchy, our root App component. As this component will be
responsible for the data for the timers, it is here that we will define the logic for handling the events
we’re capturing down at the lowest-level components.
The first event we’re concerned with is the submission of a form (i.e. events from TimerForm). When
this happens, either a new timer is being created or an existing one is being updated. We’ll create
two separate functions to handle these two distinct events:
handleCreateFormSubmit() will handle creating timers and will be the function passed to
ToggleableTimerForm
handleFormSubmit() will handle updating timers and will be the function passed to EditableTimer
Both functions travel down their respective component hierarchies until they reach TimerForm as
the prop onFormSubmit().
Let’s start with handleCreateFormSubmit().
Handling creates
handleCreateFormSubmit() will be the function App will supply to ToggleableTimerForm. Let’s set
that prop now:
React Fundamentals 111
time-tracking/3/App.js
<ToggleableTimerForm onFormSubmit={this.handleCreateFormSubmit} />
Next, we’ll define the function.
For creating timers, we’ll be using the function newTimer() from utils/TimerUtils.js. This just
hides some logic, like generating ids. Here’s what it looks like:
time-tracking/utils/TimerUtils.js
export const newTimer = (attrs = {}) => {
const timer = {
title: attrs.title || 'Timer',
project: attrs.project || 'Project',
id: uuidv4(),
elapsed: 0,
isRunning: false,
};
return timer;
};
The function accepts an object with timer and project properties and returns a new object with the
rest of the properties properly initialized.
Import that function at the top of App.js:
time-tracking/3/App.js
import { newTimer } from './utils/TimerUtils';
Inside handleCreateFormSubmit(), we’ll use newTimer() to insert a new timer object into state.
Declare this function above render():
time-tracking/3/App.js
handleCreateFormSubmit = timer => {
const { timers } = this.state;
this.setState({
timers: [newTimer(timer), ...timers],
});
};
React Fundamentals 112
Note that we set this.state.timers to a new array of timers. The first element in the array is our
new timer, created with newTimer(). Then, we use JavaScript’s spread syntax to add the rest of our
existing timers to this new array. We do this to avoid mutating state.
The tempting alternative is to write handleCreateFormSubmit() like this:
handleCreateFormSubmit = timer => {
const { timers } = this.state;
this.setState({
timers: timers.push(newTimer(timer)), // mutates state!
});
};
But .push() appends the new timer to the existing array in state. It’s subtle, but this mutates
state. And we never want to mutate state outside of the this.setState() method.
We always want to treat the state object (and the objects and arrays inside state) as
immutable. Writing immutable JavaScript can be tricky at first. A simple strategy is to just
avoid using certain Array and Object methods. .push() is one method to avoid, as it always
mutates the array it is called on.
If you still find the distinction between .push() and the spread syntax confusing, don’t
worry. We’ll be showcasing strategies for how to avoid accidental state mutations through-
out the book.
Spread syntax
In arrays, the ellipsis (…) will expand the array that follows into the parent array. The spread
operator enables us to succinctly construct new arrays as a composite of existing arrays:
const a = [ 1, 2, 3 ];
const b = [ 4, 5, 6 ];
const c = [ ...a, ...b, 7, 8, 9 ];
console.log(c); // -> [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
In objects, the ellipsis (…) will allow you to create a modified version of an existing object:
const coffee = { milk: false, cream: false };
const coffeeWithMilk = { ...coffee, milk: true };
console.log(coffeeWithMilk); // -> { milk: true, cream: false }
This can be useful when working with immutable JavaScript objects.
React Fundamentals 113
Try it out
Now we’ve finished wiring up the create timer flow from the form down in TimerForm up to the
state managed in App. Save App.js and your app should reload. Toggle open the create form and
create some new timers:
Updating timers
Our app is setup for creating timers. Let’s add updates next.
We know we’ll eventually want App to define a handler, onFormSubmit(), for when TimerForm
submits a timer update. However, as you can see in the current state of the app, we haven’t yet
added the ability for a timer to be edited. We don’t yet have a way to display an edit form which
will be a prerequisite to submitting one.
To display an edit form, the user will press on the edit button on a Timer. This should propagate an
event up to EditableTimer and tell it to flip its child component, opening the form.
We’ll work from the bottom-up again. We’ll start with Timer, specify the prop-functions that it needs,
then move up the component hierarchy.
Adding editability to Timer
Since we’ll be adding a fair bit of functionality to Timer, we’ll first convert it into a class component:
React Fundamentals 114
time-tracking/4/components/Timer.js
export default class Timer extends React.Component {
EditableTimer manages the state of whether or not the edit form is open. So, we’ll expect Timer to
receive a prop from its parent, onEditPress(). We’ll set the onPress prop on the “Edit” TimerButton
to this prop:
time-tracking/4/components/Timer.js
render() {
const { elapsed, title, project, onEditPress } = this.props;
const elapsedString = millisecondsToHuman(elapsed);
return (
<View style={styles.timerContainer}>
<Text style={styles.title}>{title}</Text>
<Text>{project}</Text>
<Text style={styles.elapsedTime}>{elapsedString}</Text>
<View style={styles.buttonGroup}>
<TimerButton color="blue" small title="Edit" onPress={onEditPress} />
<TimerButton color="blue" small title="Remove" />
</View>
<TimerButton color="#21BA45" title="Start" />
</View>
);
}
Updating EditableTimer
Now we’re prepared to update EditableTimer. Again, it will display either the TimerForm (if we’re
editing) or an individual Timer (if we’re not editing).
Let’s add event handlers for both possible child components. For TimerForm, we want to handle the
form being closed or submitted. For Timer, we want to handle the edit button being pressed:
React Fundamentals 115
time-tracking/4/components/EditableTimer.js
export default class EditableTimer extends React.Component {
state = {
editFormOpen: false,
};
handleEditPress = () => {
this.openForm();
};
handleFormClose = () => {
this.closeForm();
};
handleSubmit = timer => {
const { onFormSubmit } = this.props;
onFormSubmit(timer);
this.closeForm();
};
closeForm = () => {
this.setState({ editFormOpen: false });
};
openForm = () => {
this.setState({ editFormOpen: true });
};
We pass these event handlers down as props:
time-tracking/4/components/EditableTimer.js
render() {
const { id, title, project, elapsed, isRunning } = this.props;
const { editFormOpen } = this.state;
if (editFormOpen) {
return (
<TimerForm
id={id}
title={title}
project={project}
React Fundamentals 116
onFormSubmit={this.handleSubmit}
onFormClose={this.handleFormClose}
/>
);
}
return (
<Timer
id={id}
title={title}
project={project}
elapsed={elapsed}
isRunning={isRunning}
onEditPress={this.handleEditPress}
/>
);
}
Look a bit familiar? EditableTimer handles the same events emitted from TimerForm in a similar
manner as ToggleableTimerForm. This makes sense. Both EditableTimer and ToggleableTimerForm
are just intermediaries between TimerForm and App. App is the one that defines the submit function
handlers.
Like ToggleableTimerForm, EditableTimer doesn’t do anything with the incoming timer. In
handleSubmit(), it just blindly passes this object along to its prop-function onFormSubmit(). It then
closes the form with closeForm().
We pass along our new prop to Timer, onEditPress. The behavior for this function is defined in
handleEditPress, which modifies the state for EditableTimer, opening the form.
Defining handleFormSubmit() in App
Like we did with handleCreateFormSubmit(), the last step with this pipeline is to define a handler
for edit form submits up in App, handleFormSubmit().
For creating timers, we have a function that creates a new timer object with the specified attributes
and we prepend this new object to the beginning of the timers array in state.
For updating timers, we need to hunt through the timers array until we find the timer object that is
being updated. As always, the state object cannot be updated directly. We have to use setState().
Therefore, we’ll use map() to traverse the array of timer objects. If the timer’s id matches that of
the form submitted, we’ll return a new object that contains the timer with the updated attributes.
Otherwise we’ll just return the original timer. This new array of timer objects will be passed to
setState():
React Fundamentals 117
time-tracking/4/App.js
handleFormSubmit = attrs => {
const { timers } = this.state;
this.setState({
timers: timers.map(timer => {
if (timer.id === attrs.id) {
const { title, project } = attrs;
return {
...timer,
title,
project,
};
}
return timer;
}),
});
};
Note that we call map() on timers from within the JavaScript object we’re passing to setState().
This is an often used pattern. The call is evaluated and then the property timers is set to the result.
Inside of the map() function we check if the timer matches the one being updated by comparing
their id attributes. If not, we just return the timer. Otherwise, we use the spread operator again to
return a new object with the timer’s updated attributes.
Remember, it’s important here that we treat state as immutable. By creating a new timers object and
then using the spread operator to populate it, we’re not modifying any of the objects sitting in state.
We pass this method down as a prop inside render() to EditableTimer:
time-tracking/4/App.js
{timers.map(({ title, project, id, elapsed, isRunning }) => (
<EditableTimer
key={id}
id={id}
title={title}
project={project}
elapsed={elapsed}
isRunning={isRunning}
onFormSubmit={this.handleFormSubmit}
/>
))}
React Fundamentals 118
As we did with ToggleableTimerForm and handleCreateFormSubmit, we pass down handleFormSubmit
as the prop onFormSubmit. TimerForm calls this prop, oblivious to the fact that this function is entirely
different when it is rendered underneath EditableTimer as opposed to ToggleableTimerForm.
Try it out
Both of the forms are wired up! Save App.js, and after your app reloads, try both creating and
updating timers. You can also press “Cancel” on an open form to close it:
Note that the keyboard might get in the way when you’re typing into an edit form. We’ll address
this at the end of the chapter by using the KeyboardAvoidingView component in App.
The rest of our work resides within the timer. We need to:
Wire up the “Remove” button
Implement the start/stop buttons and the timing logic itself
Try it yourself
Feeling ambitious? Before moving on to the next section, see how far you can get wiring up
the “Remove” button by yourself. Move ahead afterwards and verify your solution is sound.
React Fundamentals 119
Deleting timers
Adding the event handler to Timer
As with adding create and update functionality, we’ll work from the bottom-up. We’ll start in Timer
and work our way up to App. In App is where we’ll define the function that removes the targeted
timer from state.
In Timer, we’ll begin by defining the function for handling “Remove” button press events:
time-tracking/5/components/Timer.js
handleRemovePress = () => {
const { id, onRemovePress } = this.props;
onRemovePress(id);
};
We’ve yet to define the function that will be set as the prop onRemovePress(). But you can imagine
that when this event reaches the top (App), we’re going to need the id to sort out which timer is
being deleted. The handleRemovePress() method provides the id to this function.
We use onPress to connect that function to the “Remove TimerButton:
time-tracking/5/components/Timer.js
<TimerButton
color="blue"
small
title="Remove"
onPress={this.handleRemovePress}
/>
Routing through EditableTimer
In EditableTimer, we include onRemovePress in the destructured props in the component’s render
method:
React Fundamentals 120
time-tracking/5/components/EditableTimer.js
render() {
const {
id,
title,
project,
elapsed,
isRunning,
onRemovePress,
} = this.props;
We then pass the function along to Timer:
time-tracking/5/components/EditableTimer.js
<Timer
id={id}
title={title}
project={project}
elapsed={elapsed}
isRunning={isRunning}
onEditPress={this.handleEditPress}
onRemovePress={onRemovePress}
/>
Implementing the remove function in App
The last step is to define the function in App that removes the desired timer from the state array.
There are many ways to accomplish this in JavaScript. If you attempted to implement this solution
on your own, don’t sweat it if your solution was not the same.
We add our handler function that we will ultimately pass down as a prop:
time-tracking/5/App.js
handleRemovePress = timerId => {
this.setState({
timers: this.state.timers.filter(t => t.id !== timerId),
});
};
Here, we use the Array filter() method to return a new array without the timer object that has an
id matching timerId.
Finally, we pass down handleRemovePress() as a prop:
React Fundamentals 121
time-tracking/5/App.js
<EditableTimer
key={id}
id={id}
title={title}
project={project}
elapsed={elapsed}
isRunning={isRunning}
onFormSubmit={this.handleFormSubmit}
onRemovePress={this.handleRemovePress}
/>
Array filter()
Array filter() accepts a function that is used to “test” each element in the array. It returns
a new array containing all the elements that “passed” the test. If the function returns true,
the element is kept.
Try it out
Save App.js and reload the app. Now you can delete timers:
React Fundamentals 122
Adding timing functionality
Functionality for creating, updating, and deleting is now in place for our timers. The next challenge:
making these timers actually track time.
There are several different ways we can implement a timer system. The simplest approach would
be to have a function update the elapsed property on each timer every second. This is why we’ve
included the timer property isRunning. We can do something like this:
this.setState({
timers: timers.map(timer => {
const { elapsed, isRunning } = timer;
return {
...timer,
elapsed: isRunning ? elapsed + 1000 : elapsed,
};
}),
});
We map through all the timers in state and check the value of isRunning. If isRunning is true, we
can add 1000 milliseconds (or 1 second) to elapsed.
Now, to make this work we’ll need to do this every second. We can use JavaScript’s setInterval()
to execute this function on an interval.
Let’s set up our interval in componentDidMount:
time-tracking/6/App.js
componentDidMount() {
const TIME_INTERVAL = 1000;
this.intervalId = setInterval(() => {
const { timers } = this.state;
this.setState({
timers: timers.map(timer => {
const { elapsed, isRunning } = timer;
return {
...timer,
elapsed: isRunning ? elapsed + TIME_INTERVAL : elapsed,
};
}),
React Fundamentals 123
});
}, TIME_INTERVAL);
}
setInterval() accepts two arguments. The first argument is the function we’d like executed on an
interval. Here, we’re performing the logic to update elapsed. The second argument is the length
of the interval (or the delay between function invocations). We set that to TIME_INTERVAL, 1000
milliseconds.
We also capture the return value of setInterval(), setting the component variable this.intervalId.
This special identifier allows us to stop the interval at any point in the future using JavaScript’s cor-
responding clearInterval(). We’ll want to cancel (or “clear”) this interval if the timer component
is ever unmounted (deleted). Otherwise, our function will run on indefinitely and cause errors. We
can use the componentWillUnmount lifecycle hook for this:
time-tracking/6/App.js
componentWillUnmount() {
clearInterval(this.intervalId);
}
This is the first time we’re using componentWillUnmount. Like the name suggests, this method fires
right before a component is unmounted or removed. In this example, we use clearInterval() to
cancel the logic that updates our timers.
Using setInterval() when a component mounts and clearInterval() when a component un-
mounts is a common pattern in React apps that require interval events.
In our version of the app, App will never be unmounted. However, it is still best practice
to clear any intervals when a component unmounts. This “future proofs” our app. There
are many libraries, like “hot reloading” libraries, that would cause even the app’s main
component to be unmounted and re-mounted.
Although this timer implementation works for our purposes, it is not the most accurate.
There is no guarantee that timers will be updated precisely every 1000 milliseconds and we
lose accuracy around starts and stops.
An example of a more precise approach would be defining a separate timer attribute, like
runningSince. We could then derive how long a timer has been running by calculating the
difference between the value of runningSince and the current time. If we saved this value
somewhere, it would also allow our timers to continue “running” even while the app is
closed.
React Fundamentals 124
Add start and stop functionality
With our interval in place, we just need to add the ability to flip the isRunning boolean on a given
timer.
The action button at the bottom of each timer should display “Start” if the timer is paused and
“Stop if the timer is running. These presses will invoke functions defined in App that will modify
isRunning.
Add timer action events to
Timer
We’ll start at the bottom again with Timer.
We’ll anticipate two prop-functions, onStartPress() and onStopPress(). Let’s write the button
press event handlers that will call these functions first:
time-tracking/6/components/Timer.js
handleStartPress = () => {
const { id, onStartPress } = this.props;
onStartPress(id);
};
handleStopPress = () => {
const { id, onStopPress } = this.props;
onStopPress(id);
};
We’re propagating the id property up to App so it knows which timer to start or stop.
Inside render(), we’ll anticipate a renderActionButton() method that conditionally shows the
correct button based on whether the timer is running or has stopped:
React Fundamentals 125
time-tracking/6/components/Timer.js
render() {
const { elapsed, title, project, onEditPress } = this.props;
const elapsedString = millisecondsToHuman(elapsed);
return (
<View style={styles.timerContainer}>
<Text style={styles.title}>{title}</Text>
<Text>{project}</Text>
<Text style={styles.elapsedTime}>{elapsedString}</Text>
<View style={styles.buttonGroup}>
<TimerButton color="blue" small title="Edit" onPress={onEditPress} />
<TimerButton
color="blue"
small
title="Remove"
onPress={this.handleRemovePress}
/>
</View>
{this.renderActionButton()}
</View>
);
}
Now let’s set up the JSX rendered within this method:
time-tracking/6/components/Timer.js
renderActionButton() {
const { isRunning } = this.props;
if (isRunning) {
return (
<TimerButton
color="#DB2828"
title="Stop"
onPress={this.handleStopPress}
/>
);
}
return (
<TimerButton
React Fundamentals 126
color="#21BA45"
title="Start"
onPress={this.handleStartPress}
/>
);
}
We could write this conditional inside of the component’s render() method. But, as we briefly
mentioned in the previous chapter, a common pattern in React is to use helper methods to do this.
Sometimes this helps with code clarity and readability. In renderActionButton(), we specifically
render one button or another based on this.props.isRunning.
Now we’ll need to run these events up the component hierarchy, all the way up to App where we’re
managing state.
Run the events through EditableTimer
In EditableTimer, we’ll need to pass onStartPress and onStopPress to Timer:
time-tracking/6/components/EditableTimer.js
render() {
const {
id,
title,
project,
elapsed,
isRunning,
onRemovePress,
onStartPress,
onStopPress,
} = this.props;
const { editFormOpen } = this.state;
if (editFormOpen) {
return (
<TimerForm
id={id}
title={title}
project={project}
onFormSubmit={this.handleSubmit}
onFormClose={this.handleFormClose}
/>
);
React Fundamentals 127
}
return (
<Timer
id={id}
title={title}
project={project}
elapsed={elapsed}
isRunning={isRunning}
onEditPress={this.handleEditPress}
onRemovePress={onRemovePress}
onStartPress={onStartPress}
onStopPress={onStopPress}
/>
);
}
We can define a single function that handles these props in App. It should hunt through the state
timers array using map, flipping isRunning when it finds the matching timer:
time-tracking/6/App.js
toggleTimer = timerId => {
this.setState(prevState => {
const { timers } = prevState;
return {
timers: timers.map(timer => {
const { id, isRunning } = timer;
if (id === timerId) {
return {
...timer,
isRunning: !isRunning,
};
}
return timer;
}),
};
});
};
When toggleTimer comes across the relevant timer within its map call, it sets the property isRunning
to the opposite of its value. This means it will stop a running timer and start a stopped timer.
React Fundamentals 128
Finally, we pass this function down to EditableTimer in the render method:
time-tracking/6/App.js
<EditableTimer
key={id}
id={id}
title={title}
project={project}
elapsed={elapsed}
isRunning={isRunning}
onFormSubmit={this.handleFormSubmit}
onRemovePress={this.handleRemovePress}
onStartPress={this.toggleTimer}
onStopPress={this.toggleTimer}
/>
Try it out
Save App.js, wait for the app to reload, and behold: you can now create, update, and delete timers
as well as actually use them to time things!
Again, for this app we won’t add server communication. Without it, our app’s data is ephemeral. If
we reload the app, the timers will reset.
Wrapping App in KeyboardAvoidingView
A behavioral quirk in our app so far has been that the keyboard can get in the way when we edit a
timer. As we saw in the first chapter, we can wrap our app in a KeyboardAvoidingView component
React Fundamentals 129
to address this.
In App.js, first import the component:
time-tracking/6/App.js
import React from 'react';
import uuidv4 from 'uuid/v4';
import {
StyleSheet,
View,
ScrollView,
Text,
KeyboardAvoidingView,
Next, wrap the ScrollView component with it:
time-tracking/6/App.js
render() {
const { timers } = this.state;
return (
<View style={styles.appContainer}>
<View style={styles.titleContainer}>
<Text style={styles.title}>Timers</Text>
</View>
<KeyboardAvoidingView
behavior="padding"
style={styles.timerListContainer}
>
<ScrollView contentContainerStyle={styles.timerList}>
<ToggleableTimerForm onFormSubmit={this.handleCreateFormSubmit} />
{timers.map(({ title, project, id, elapsed, isRunning }) => (
<EditableTimer
key={id}
id={id}
title={title}
project={project}
elapsed={elapsed}
isRunning={isRunning}
onFormSubmit={this.handleFormSubmit}
onRemovePress={this.handleRemovePress}
onStartPress={this.toggleTimer}
React Fundamentals 130
onStopPress={this.toggleTimer}
/>
))}
</ScrollView>
</KeyboardAvoidingView>
</View>
);
}
Finally, add this style to the styles object:
time-tracking/6/App.js
timerListContainer: {
flex: 1,
},
Our ScrollView will now accommodate the keyboard when we start typing into text inputs.
To finish up, let’s add PropTypes to our components. As discussed in the previous chapter, PropTypes
are nice to have in place when making additions or changes to a React Native app.
PropTypes
Let’s add PropTypes to each of our components beginning with EditableTimer:
time-tracking/components/EditableTimer.js
export default class EditableTimer extends React.Component {
static propTypes = {
id
: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
project: PropTypes.string.isRequired,
elapsed: PropTypes.number.isRequired,
isRunning: PropTypes.bool.isRequired,
onFormSubmit: PropTypes.func.isRequired,
onRemovePress: PropTypes.func.isRequired,
onStartPress: PropTypes.func.isRequired,
onStopPress: PropTypes.func.isRequired,
};
For this component, all our props are required as we’re expecting App to always set them. Now let’s
take a look at Timer:
React Fundamentals 131
time-tracking/components/Timer.js
export default class Timer extends Component {
static propTypes = {
id: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
project: PropTypes.string.isRequired,
elapsed: PropTypes.number.isRequired,
isRunning: PropTypes.bool.isRequired,
onEditPress: PropTypes.func.isRequired,
onRemovePress: PropTypes.func.isRequired,
onStartPress: PropTypes.func.isRequired,
onStopPress: PropTypes.func.isRequired,
};
Similarly, we know each of the props for Timer should always be provided by EditableTimer. Let’s
add PropTypes and defaultProps for TimerButton:
time-tracking/components/TimerButton.js
TimerButton.propTypes = {
color: ColorPropType.isRequired,
title: PropTypes.string.isRequired,
small: PropTypes.bool,
onPress: PropTypes.func.isRequired,
};
TimerButton.defaultProps = {
small: false,
};
small is an optional prop with a default value of false. Our other props are all required. We’re also
using ColorPropType for our color prop in order to correctly validate if an appropriate color
54
is
passed in. We’ll need to import it at the top of our file from react-native as well.
Our last two components that need prop validations are our form components. Let’s begin with
TimerForm:
54
https://facebook.github.io/react-native/docs/next/colors.html
React Fundamentals 132
time-tracking/components/TimerForm.js
export default class TimerForm extends React.Component {
static propTypes = {
id: PropTypes.string,
title: PropTypes.string,
project: PropTypes.string,
onFormSubmit: PropTypes.func.isRequired,
onFormClose: PropTypes.func.isRequired,
};
static defaultProps = {
id: null,
title: '',
project: '',
};
For this component, we only pass in timer attributes (id, title, and project) if we’re editing
a timer form and not creating one. We’ve added appropriate default values for each. Now for
ToggleableTimerForm:
time-tracking/components/ToggleableTimerForm.js
export default class ToggleableTimerForm extends Component {
static propTypes = {
onFormSubmit: PropTypes.func.isRequired,
};
This component only takes a single required prop, onFormSubmit, which fires when we submit our
timer form.
Methodology review
While building our time-tracking app, we learned and applied a methodology for building React
apps. Again, those steps were:
1. Break the app into components
We mapped out the component structure of our app by examining the app’s working UI. We
then applied the single-responsibility principle to break components down so that each had
minimal viable functionality.
2. Build a static version of the app
Our bottom-level (user-visible) components rendered JSX based on static props, passed down
from parents.
React Fundamentals 133
3. Determine what should be stateful
We used a series of questions to deduce what data should be stateful. This data was represented
in our static app as props.
4. Determine in which component each piece of state should live
We used another series of questions to determine which component should own each piece of
state. App owned timer state data and ToggleableTimerForm and EditableTimer both held state
pertaining to whether or not to render a TimerForm.
5. Hardcode initial states
For the components that own state, we initialized state properties with hardcoded values.
6. Add inverse data flow
We added interactivity by decorating buttons with onPress handlers. These called functions
that were passed in as props down the hierarchy from whichever component owned the
relevant state being manipulated.
If we were planning to add server communication to our application, it would make sense to do it
now given we’ve completed setting up the base of our entire application.
Up next
With the first chapter, we explored the basics of React Native by building a weather app. In this
chapter, we dove deeper into the fundamentals of the React API by creating a more interactive
application with more components. Although we covered a number of important concepts including
a useful pattern for building React Native apps from scratch, we’ve so far only briefly covered each
of React Native’s built-in components, like View and Text. Over the next two chapters, we’ll examine
a number of React Native’s core components in greater detail.
Core Components, Part 1
What are components?
Components are the building blocks of any React Native application. We used components like View
and Text throughout the previous chapters to create the UI for our weather app and our timer app.
Out-of-the-box, React Native includes components for everything from form controls to rich media.
Up to this point, we’ve been using React Native components without fully exploring how they
work. In this chapter, we’ll study the most common built-in React Native components. Just as in
the previous chapters, we’ll build an application as we go. When we come across a new topic, we’ll
deep dive into that topic before we keep building. At the end of the chapter, you should have a solid
foundation of knowledge for using any React Native component even the ones we don’t cover will
follow many of the same patterns.
UI abstraction
Components are an abstraction layer on top of the underlying native platform. On an iOS device,
a React Native component is ultimately rendered as a UIView. On Android, the same component
would be rendered as an android.view. As React Native expands to new platforms, the same code
should be able to render correctly on more and more devices.
React Native is already supported on the universal Windows platform
55
, Apple TV (part of
the main react-native repository
56
), React VR
57
, and the web
58
.
As you start building complex apps, you’ll likely run into cases where you want to use a feature that
exists on one platform but not the other. Platform-specific components exist for cases like these.
Generally, the component’s name will end with the name of the platform e.g. NavigatorIOS. As
we mentioned in the “Getting Started”, there are several ways to run different code on different
platforms you will need to do this for platform-specific components.
Building an Instagram clone
In this chapter, we’ll use the most common React Native components to build an app that resembles
Instagram. We’ll build the main image feed with the components View, Text, Image and FlatList.
We’ll also build a comments screen using TextInput and ScrollView.
55
https://github.com/Microsoft/react-native-windows
56
https://github.com/facebook/react-native
57
https://facebook.github.io/react-vr/
58
https://github.com/necolas/react-native-web
Core Components, Part 1 135
To try the completed app on your phone:
On Android, you can scan this QR code from within the Expo app:
On iOS, you can navigate to the image-feed/ directory within our sample code folder and build
the app using the same process in previous chapters. You can either preview it using the iOS
simulator or send the link of the project URL to your device.
Our app will have two screens. The first screen is the image feed:
Core Components, Part 1 136
The second screen opens when we tap “3 comments” to display comments for that image:
Project setup
Just as we did in the previous chapters, let’s create a new app with the following command:
$ create-react-native-app image-feed --scripts-version 1.14.0
Once this finishes, navigate into the image-feed directory.
Choose one of the following to start the app:
yarn start - Start the Packager and display a QR code to open the app on your Android phone
yarn ios - Start the Packager and launch the app on the iOS simulator
yarn android - Start the Packager and launch the app on the Android emulator
You should see the default App.js file running, which looks like this:
Core Components, Part 1 137
Now’s a good time to copy over the image-feed/utils directory from the sample code into your
own project. Copy the utils directory into the image-feed directory we just created.
How we’ll work
In this chapter, we’ll build our app following the same methodology as the previous chapter. We’ll
break the app into components, build them statically, and so on. We won’t specifically call out each
step, since it isn’t necessary to follow them exactly. They’re most useful as a reference for when
you’re unsure what to do next.
If at any point you get stuck when building an app of your own, consider identifying which
steps you’ve completed, and following the steps more closely until you’re back on track.
Breaking down the feed screen
We want to start thinking about our app in terms of the different components of our UI. Ultimately
our app will render built-in components like View and Text, but as we learned in the previous chapter,
it’s useful to build higher levels of abstraction on top of these. Let’s start by figuring out how our
main image feed might break down into components.
Core Components, Part 1 138
A good component is generally concise and self-contained. By looking at the screenshot we are trying
to build, we can identify which pieces are reasonably distinct from others and reused in multiple
places. Since we’re only building a couple screens, we won’t be able to make fully informed decisions
about which parts of the screenshots are most reusable as we don’t know what the other screens
in the app will look like. But we can make some pretty good guesses. Here’s one way we can break
down the main feed:
Avatar - The profile photo or initials of the author of the image
AuthorRow - The horizontal row containing info about the author: their avatar and their name
Card - The item in the image feed containing the image and info about its author
CardList - The list of cards in the feed
Each of these build upon one another: CardList contains a list of Card components, which each
contain an AuthorRow, which contains an Avatar.
Core Components, Part 1 139
Top-down vs. bottom-up
When it comes to building the UI components of an app, there are generally two approaches: top-
down and bottom-up. In a top-down approach, we would start by building the CardList component,
and then we would build the components within the CardList, and then the components within
those, and so on until we reach the inner-most component, Avatar. In a bottom-up approach, we
would start with the innermost components like Avatar, and keep building up higher levels of
abstraction until we get to the CardList. Choosing between these two approaches is mostly personal
preference, and it’s common to do a little of both.
For this app, we’re going to work bottom-up. We’ll start with the Avatar component, and then build
the AuthorRow which uses it, and so on.
Unlike the last chapter, we’ll focus on building one component at a time, testing each one as we go.
We can modify App.js to render just the component we’re currently working on.
As an example, if we were to do this for the Avatar component, we might modify the App.js file to
render just the Avatar:
// Inside App.js
render() {
return <Avatar />;
}
We might also hardcode different kinds of props for testing:
// Inside App.js
render() {
return <Avatar initials="FL" size={35} backgroundColor={'blue'} />;
}
Isolating individual components like this is a useful technique when working with styles. A
component’s layout can change based on its parent if we build a component within a specific
parent, we may end up with styles that closely couple the parent and child. This isn’t ideal, since
we want our components to look accurate within any parent for better reusability. We can easily
ensure that components work well anywhere by building components at the top level of the view
hierarchy, since the top level has the default layout configuration.
Now that we have our strategy locked down, let’s start with the Avatar component.
Avatar
Here’s what the Avatar should look like, when rendered in isolation:
Core Components, Part 1 140
For simple apps, it’s easiest to keep all of our components together in a
components
directory.
For more advanced apps, we might create directories within components to categorize them more
specifically. Since this app is pretty simple, let’s use a flat components directory, just like we did in
the previous chapters.
Let’s create a new directory called components and create a new file within that called Avatar.js.
Our avatar component is going to render the components View and Text. It’s going to use StyleSheet,
and it’s going to validate strings, numbers, and color props with PropTypes. Let’s import these things
at the top of the file. We also have to import React.
We’ll import React in this file, even though we don’t reference it anywhere. Behind-the-
scenes, babel compiles JSX elements into calls to React.createElement, which reference the
React variable.
Add the following imports to Avatar.js:
Core Components, Part 1 141
image-feed/1/components/Avatar.js
import { ColorPropType, StyleSheet, Text, View } from 'react-native';
import PropTypes from 'prop-types';
import React from 'react';
We import ColorPropType from react-native rather than PropTypes. The PropTypes
package contains validators for primitive JavaScript types like numbers and strings. While
colors in React Native are strings, they follow a specific format that can be validated React
Native provides a handful of validators like ColorPropType for validating the contents of a
value rather than just its primitive type.
Now we can export the skeleton of our component:
image-feed/1/components/Avatar.js
export default function Avatar({ /* ... */ }) {
// ...
}
Since this component won’t need to store any local state, we’ll use the stateless functional
component style that we learned about in the previous chapter.
What should the props be for our avatar? We definitely need the initials to render. We also probably
want the size and background color to be configurable. With that in mind, we can define our
propTypes like this:
image-feed/1/components/Avatar.js
// ...
export default function Avatar({ size, backgroundColor, initials }) {
// ...
}
Avatar.propTypes = {
initials: PropTypes.string.isRequired,
size: PropTypes.number.isRequired,
backgroundColor: ColorPropType.isRequired,
};
// ...
Core Components, Part 1 142
In this app, we’ll make most of our props required using isRequired, since we’ll always pass every
prop. If we wanted to make our component more reusable, we could instead make its props optional
but it’s hard to know which props should be optional until we actually try to reuse it!
It’s time to render the contents of our Avatar. For the colored circular background, we’ll render a
View. The View is the most common and versatile component. We’ve already used it throughout the
previous chapters, but now let’s take a closer look at how it works and how to style it.
View
There are two fairly distinct things we use View for:
First, we use View for layout. A View is commonly used as a container for other components. If
we want to arrange a group of components vertically or horizontally, we will likely wrap those
components in a View.
Second, we use View for styling our app. If we want to render a simple shape like a circle or
rectangle, or if we want to render a border, a line, or a background color, we will likely use a
View.
React Native components aim to be as consistent as possible many components use similar props
as the View, such as style. Because of this, if you learn how to work with View, you can reuse that
knowledge with Text, Image, and nearly every other kind of component.
Avatar background
Let’s use View to create the circular background for our Avatar:
image-feed/1/components/Avatar.js
// ...
export default function Avatar({ size, backgroundColor, initials }) {
const style = {
width: size,
height: size,
borderRadius: size / 2,
backgroundColor,
}
return (
<View style={style} />
)
}
Core Components, Part 1 143
// ...
As we saw in previous chapters, we can use the style prop to customize the dimensions and colors
of our View component. Here, we instantiate a new object that we pass to the style prop of our
View. We can assign the size prop to the width and height attributes to specify that our View should
always be rendered as a perfect square. Adding a borderRadius that’s half the size of the width and
height will render our View as a circle. Lastly, we set the background color.
In this style object, the attributes are computed dynamically: width, height, borderRadius, and
backgroundColor are all derived from the component’s props. When we compute style objects
dynamically (i.e. when rendering our component), we define them inline this means we create
a new style object every time the component is rendered, and pass it directly to the style prop of
our component.
When there are a lot of style objects defined inline, it can clutter the render method, making the
code harder to follow. For styles which aren’t computed dynamically, we should use the StyleSheet
API. We’ll practice this more in the next few sections.
Before that, let’s make sure what we have so far is working correctly.
Try it out
Let’s add our Avatar component to App. We haven’t finished Avatar yet, but it’s useful to test as we
go in case we’ve introduced any errors.
Open up App.js and import our Avatar after our other imports:
image-feed/1/App.js
import Avatar from './components/Avatar';
Next, modify the render function to render an Avatar:
image-feed/1/App.js
// ...
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<Avatar initials={'FL'} size={35} backgroundColor={'teal'} />
</View>
);
}
Core Components, Part 1 144
}
// ...
For any props we didn’t include, the Avatar will use its defaultProps. We should see a 35px teal
circle in the center of the screen:
Regardless of the size of your screen, the teal circle will render in the center. This means React
Native is calculating the center of the screen, calculating the dimensions of the Avatar, and using
these calculations to properly position the View component. As we learned in the “Getting Started”
chapter, the React Native layout engine is based on the flexbox algorithm. Let’s start digging into
how layout works: how does React Native know the dimensions for each component and where to
render it on the screen?
Dimensions
The first thing we want to think about when understanding the layout of a screen is the dimensions
of each component. A component must have both a non-zero width and height in order to render
anything on the screen. If the width is 0, then nothing will render on the screen, no matter how large
the height is.